Field.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. var __defProp = Object.defineProperty;
  2. var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
  3. var __getOwnPropNames = Object.getOwnPropertyNames;
  4. var __hasOwnProp = Object.prototype.hasOwnProperty;
  5. var __export = (target, all) => {
  6. for (var name2 in all)
  7. __defProp(target, name2, { get: all[name2], enumerable: true });
  8. };
  9. var __copyProps = (to, from, except, desc) => {
  10. if (from && typeof from === "object" || typeof from === "function") {
  11. for (let key of __getOwnPropNames(from))
  12. if (!__hasOwnProp.call(to, key) && key !== except)
  13. __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  14. }
  15. return to;
  16. };
  17. var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
  18. var stdin_exports = {};
  19. __export(stdin_exports, {
  20. default: () => stdin_default,
  21. fieldProps: () => fieldProps,
  22. fieldSharedProps: () => fieldSharedProps
  23. });
  24. module.exports = __toCommonJS(stdin_exports);
  25. var import_vue = require("vue");
  26. var import_utils = require("../utils");
  27. var import_utils2 = require("./utils");
  28. var import_Cell = require("../cell/Cell");
  29. var import_use = require("@vant/use");
  30. var import_use_id = require("../composables/use-id");
  31. var import_use_expose = require("../composables/use-expose");
  32. var import_icon = require("../icon");
  33. var import_cell = require("../cell");
  34. const [name, bem] = (0, import_utils.createNamespace)("field");
  35. const fieldSharedProps = {
  36. id: String,
  37. name: String,
  38. leftIcon: String,
  39. rightIcon: String,
  40. autofocus: Boolean,
  41. clearable: Boolean,
  42. maxlength: import_utils.numericProp,
  43. max: Number,
  44. min: Number,
  45. formatter: Function,
  46. clearIcon: (0, import_utils.makeStringProp)("clear"),
  47. modelValue: (0, import_utils.makeNumericProp)(""),
  48. inputAlign: String,
  49. placeholder: String,
  50. autocomplete: String,
  51. autocapitalize: String,
  52. autocorrect: String,
  53. errorMessage: String,
  54. enterkeyhint: String,
  55. clearTrigger: (0, import_utils.makeStringProp)("focus"),
  56. formatTrigger: (0, import_utils.makeStringProp)("onChange"),
  57. spellcheck: {
  58. type: Boolean,
  59. default: null
  60. },
  61. error: {
  62. type: Boolean,
  63. default: null
  64. },
  65. disabled: {
  66. type: Boolean,
  67. default: null
  68. },
  69. readonly: {
  70. type: Boolean,
  71. default: null
  72. },
  73. inputmode: String
  74. };
  75. const fieldProps = (0, import_utils.extend)({}, import_Cell.cellSharedProps, fieldSharedProps, {
  76. rows: import_utils.numericProp,
  77. type: (0, import_utils.makeStringProp)("text"),
  78. rules: Array,
  79. autosize: [Boolean, Object],
  80. labelWidth: import_utils.numericProp,
  81. labelClass: import_utils.unknownProp,
  82. labelAlign: String,
  83. showWordLimit: Boolean,
  84. errorMessageAlign: String,
  85. colon: {
  86. type: Boolean,
  87. default: null
  88. }
  89. });
  90. var stdin_default = (0, import_vue.defineComponent)({
  91. name,
  92. props: fieldProps,
  93. emits: ["blur", "focus", "clear", "keypress", "clickInput", "endValidate", "startValidate", "clickLeftIcon", "clickRightIcon", "update:modelValue"],
  94. setup(props, {
  95. emit,
  96. slots
  97. }) {
  98. const id = (0, import_use_id.useId)();
  99. const state = (0, import_vue.reactive)({
  100. status: "unvalidated",
  101. focused: false,
  102. validateMessage: ""
  103. });
  104. const inputRef = (0, import_vue.ref)();
  105. const clearIconRef = (0, import_vue.ref)();
  106. const customValue = (0, import_vue.ref)();
  107. const {
  108. parent: form
  109. } = (0, import_use.useParent)(import_utils.FORM_KEY);
  110. const getModelValue = () => {
  111. var _a;
  112. return String((_a = props.modelValue) != null ? _a : "");
  113. };
  114. const getProp = (key) => {
  115. if ((0, import_utils.isDef)(props[key])) {
  116. return props[key];
  117. }
  118. if (form && (0, import_utils.isDef)(form.props[key])) {
  119. return form.props[key];
  120. }
  121. };
  122. const showClear = (0, import_vue.computed)(() => {
  123. const readonly = getProp("readonly");
  124. if (props.clearable && !readonly) {
  125. const hasValue = getModelValue() !== "";
  126. const trigger = props.clearTrigger === "always" || props.clearTrigger === "focus" && state.focused;
  127. return hasValue && trigger;
  128. }
  129. return false;
  130. });
  131. const formValue = (0, import_vue.computed)(() => {
  132. if (customValue.value && slots.input) {
  133. return customValue.value();
  134. }
  135. return props.modelValue;
  136. });
  137. const showRequiredMark = (0, import_vue.computed)(() => {
  138. var _a;
  139. const required = getProp("required");
  140. if (required === "auto") {
  141. return (_a = props.rules) == null ? void 0 : _a.some((rule) => rule.required);
  142. }
  143. return required;
  144. });
  145. const runRules = (rules) => rules.reduce((promise, rule) => promise.then(() => {
  146. if (state.status === "failed") {
  147. return;
  148. }
  149. let {
  150. value
  151. } = formValue;
  152. if (rule.formatter) {
  153. value = rule.formatter(value, rule);
  154. }
  155. if (!(0, import_utils2.runSyncRule)(value, rule)) {
  156. state.status = "failed";
  157. state.validateMessage = (0, import_utils2.getRuleMessage)(value, rule);
  158. return;
  159. }
  160. if (rule.validator) {
  161. if ((0, import_utils2.isEmptyValue)(value) && rule.validateEmpty === false) {
  162. return;
  163. }
  164. return (0, import_utils2.runRuleValidator)(value, rule).then((result) => {
  165. if (result && typeof result === "string") {
  166. state.status = "failed";
  167. state.validateMessage = result;
  168. } else if (result === false) {
  169. state.status = "failed";
  170. state.validateMessage = (0, import_utils2.getRuleMessage)(value, rule);
  171. }
  172. });
  173. }
  174. }), Promise.resolve());
  175. const resetValidation = () => {
  176. state.status = "unvalidated";
  177. state.validateMessage = "";
  178. };
  179. const endValidate = () => emit("endValidate", {
  180. status: state.status,
  181. message: state.validateMessage
  182. });
  183. const validate = (rules = props.rules) => new Promise((resolve) => {
  184. resetValidation();
  185. if (rules) {
  186. emit("startValidate");
  187. runRules(rules).then(() => {
  188. if (state.status === "failed") {
  189. resolve({
  190. name: props.name,
  191. message: state.validateMessage
  192. });
  193. endValidate();
  194. } else {
  195. state.status = "passed";
  196. resolve();
  197. endValidate();
  198. }
  199. });
  200. } else {
  201. resolve();
  202. }
  203. });
  204. const validateWithTrigger = (trigger) => {
  205. if (form && props.rules) {
  206. const {
  207. validateTrigger
  208. } = form.props;
  209. const defaultTrigger = (0, import_utils.toArray)(validateTrigger).includes(trigger);
  210. const rules = props.rules.filter((rule) => {
  211. if (rule.trigger) {
  212. return (0, import_utils.toArray)(rule.trigger).includes(trigger);
  213. }
  214. return defaultTrigger;
  215. });
  216. if (rules.length) {
  217. validate(rules);
  218. }
  219. }
  220. };
  221. const limitValueLength = (value) => {
  222. var _a;
  223. const {
  224. maxlength
  225. } = props;
  226. if ((0, import_utils.isDef)(maxlength) && (0, import_utils2.getStringLength)(value) > +maxlength) {
  227. const modelValue = getModelValue();
  228. if (modelValue && (0, import_utils2.getStringLength)(modelValue) === +maxlength) {
  229. return modelValue;
  230. }
  231. const selectionEnd = (_a = inputRef.value) == null ? void 0 : _a.selectionEnd;
  232. if (state.focused && selectionEnd) {
  233. const valueArr = [...value];
  234. const exceededLength = valueArr.length - +maxlength;
  235. valueArr.splice(selectionEnd - exceededLength, exceededLength);
  236. return valueArr.join("");
  237. }
  238. return (0, import_utils2.cutString)(value, +maxlength);
  239. }
  240. return value;
  241. };
  242. const updateValue = (value, trigger = "onChange") => {
  243. var _a, _b;
  244. const originalValue = value;
  245. value = limitValueLength(value);
  246. const limitDiffLen = (0, import_utils2.getStringLength)(originalValue) - (0, import_utils2.getStringLength)(value);
  247. if (props.type === "number" || props.type === "digit") {
  248. const isNumber = props.type === "number";
  249. value = (0, import_utils.formatNumber)(value, isNumber, isNumber);
  250. if (trigger === "onBlur" && value !== "" && (props.min !== void 0 || props.max !== void 0)) {
  251. const adjustedValue = (0, import_utils.clamp)(+value, (_a = props.min) != null ? _a : -Infinity, (_b = props.max) != null ? _b : Infinity);
  252. value = adjustedValue.toString();
  253. }
  254. }
  255. let formatterDiffLen = 0;
  256. if (props.formatter && trigger === props.formatTrigger) {
  257. const {
  258. formatter,
  259. maxlength
  260. } = props;
  261. value = formatter(value);
  262. if ((0, import_utils.isDef)(maxlength) && (0, import_utils2.getStringLength)(value) > +maxlength) {
  263. value = (0, import_utils2.cutString)(value, +maxlength);
  264. }
  265. if (inputRef.value && state.focused) {
  266. const {
  267. selectionEnd
  268. } = inputRef.value;
  269. const bcoVal = (0, import_utils2.cutString)(originalValue, selectionEnd);
  270. formatterDiffLen = (0, import_utils2.getStringLength)(formatter(bcoVal)) - (0, import_utils2.getStringLength)(bcoVal);
  271. }
  272. }
  273. if (inputRef.value && inputRef.value.value !== value) {
  274. if (state.focused) {
  275. let {
  276. selectionStart,
  277. selectionEnd
  278. } = inputRef.value;
  279. inputRef.value.value = value;
  280. if ((0, import_utils.isDef)(selectionStart) && (0, import_utils.isDef)(selectionEnd)) {
  281. const valueLen = (0, import_utils2.getStringLength)(value);
  282. if (limitDiffLen) {
  283. selectionStart -= limitDiffLen;
  284. selectionEnd -= limitDiffLen;
  285. } else if (formatterDiffLen) {
  286. selectionStart += formatterDiffLen;
  287. selectionEnd += formatterDiffLen;
  288. }
  289. inputRef.value.setSelectionRange(Math.min(selectionStart, valueLen), Math.min(selectionEnd, valueLen));
  290. }
  291. } else {
  292. inputRef.value.value = value;
  293. }
  294. }
  295. if (value !== props.modelValue) {
  296. emit("update:modelValue", value);
  297. }
  298. };
  299. const onInput = (event) => {
  300. if (!event.target.composing) {
  301. updateValue(event.target.value);
  302. }
  303. };
  304. const blur = () => {
  305. var _a;
  306. return (_a = inputRef.value) == null ? void 0 : _a.blur();
  307. };
  308. const focus = () => {
  309. var _a;
  310. return (_a = inputRef.value) == null ? void 0 : _a.focus();
  311. };
  312. const adjustTextareaSize = () => {
  313. const input = inputRef.value;
  314. if (props.type === "textarea" && props.autosize && input) {
  315. (0, import_utils2.resizeTextarea)(input, props.autosize);
  316. }
  317. };
  318. const onFocus = (event) => {
  319. state.focused = true;
  320. emit("focus", event);
  321. (0, import_vue.nextTick)(adjustTextareaSize);
  322. if (getProp("readonly")) {
  323. blur();
  324. }
  325. };
  326. const onBlur = (event) => {
  327. state.focused = false;
  328. updateValue(getModelValue(), "onBlur");
  329. emit("blur", event);
  330. if (getProp("readonly")) {
  331. return;
  332. }
  333. validateWithTrigger("onBlur");
  334. (0, import_vue.nextTick)(adjustTextareaSize);
  335. (0, import_utils.resetScroll)();
  336. };
  337. const onClickInput = (event) => emit("clickInput", event);
  338. const onClickLeftIcon = (event) => emit("clickLeftIcon", event);
  339. const onClickRightIcon = (event) => emit("clickRightIcon", event);
  340. const onClear = (event) => {
  341. (0, import_utils.preventDefault)(event);
  342. emit("update:modelValue", "");
  343. emit("clear", event);
  344. };
  345. const showError = (0, import_vue.computed)(() => {
  346. if (typeof props.error === "boolean") {
  347. return props.error;
  348. }
  349. if (form && form.props.showError && state.status === "failed") {
  350. return true;
  351. }
  352. });
  353. const labelStyle = (0, import_vue.computed)(() => {
  354. const labelWidth = getProp("labelWidth");
  355. const labelAlign = getProp("labelAlign");
  356. if (labelWidth && labelAlign !== "top") {
  357. return {
  358. width: (0, import_utils.addUnit)(labelWidth)
  359. };
  360. }
  361. });
  362. const onKeypress = (event) => {
  363. const ENTER_CODE = 13;
  364. if (event.keyCode === ENTER_CODE) {
  365. const submitOnEnter = form && form.props.submitOnEnter;
  366. if (!submitOnEnter && props.type !== "textarea") {
  367. (0, import_utils.preventDefault)(event);
  368. }
  369. if (props.type === "search") {
  370. blur();
  371. }
  372. }
  373. emit("keypress", event);
  374. };
  375. const getInputId = () => props.id || `${id}-input`;
  376. const getValidationStatus = () => state.status;
  377. const renderInput = () => {
  378. const controlClass = bem("control", [getProp("inputAlign"), {
  379. error: showError.value,
  380. custom: !!slots.input,
  381. "min-height": props.type === "textarea" && !props.autosize
  382. }]);
  383. if (slots.input) {
  384. return (0, import_vue.createVNode)("div", {
  385. "class": controlClass,
  386. "onClick": onClickInput
  387. }, [slots.input()]);
  388. }
  389. const inputAttrs = {
  390. id: getInputId(),
  391. ref: inputRef,
  392. name: props.name,
  393. rows: props.rows !== void 0 ? +props.rows : void 0,
  394. class: controlClass,
  395. disabled: getProp("disabled"),
  396. readonly: getProp("readonly"),
  397. autofocus: props.autofocus,
  398. placeholder: props.placeholder,
  399. autocomplete: props.autocomplete,
  400. autocapitalize: props.autocapitalize,
  401. autocorrect: props.autocorrect,
  402. enterkeyhint: props.enterkeyhint,
  403. spellcheck: props.spellcheck,
  404. "aria-labelledby": props.label ? `${id}-label` : void 0,
  405. "data-allow-mismatch": "attribute",
  406. onBlur,
  407. onFocus,
  408. onInput,
  409. onClick: onClickInput,
  410. onChange: import_utils2.endComposing,
  411. onKeypress,
  412. onCompositionend: import_utils2.endComposing,
  413. onCompositionstart: import_utils2.startComposing
  414. };
  415. if (props.type === "textarea") {
  416. return (0, import_vue.createVNode)("textarea", (0, import_vue.mergeProps)(inputAttrs, {
  417. "inputmode": props.inputmode
  418. }), null);
  419. }
  420. return (0, import_vue.createVNode)("input", (0, import_vue.mergeProps)((0, import_utils2.mapInputType)(props.type, props.inputmode), inputAttrs), null);
  421. };
  422. const renderLeftIcon = () => {
  423. const leftIconSlot = slots["left-icon"];
  424. if (props.leftIcon || leftIconSlot) {
  425. return (0, import_vue.createVNode)("div", {
  426. "class": bem("left-icon"),
  427. "onClick": onClickLeftIcon
  428. }, [leftIconSlot ? leftIconSlot() : (0, import_vue.createVNode)(import_icon.Icon, {
  429. "name": props.leftIcon,
  430. "classPrefix": props.iconPrefix
  431. }, null)]);
  432. }
  433. };
  434. const renderRightIcon = () => {
  435. const rightIconSlot = slots["right-icon"];
  436. if (props.rightIcon || rightIconSlot) {
  437. return (0, import_vue.createVNode)("div", {
  438. "class": bem("right-icon"),
  439. "onClick": onClickRightIcon
  440. }, [rightIconSlot ? rightIconSlot() : (0, import_vue.createVNode)(import_icon.Icon, {
  441. "name": props.rightIcon,
  442. "classPrefix": props.iconPrefix
  443. }, null)]);
  444. }
  445. };
  446. const renderWordLimit = () => {
  447. if (props.showWordLimit && props.maxlength) {
  448. const count = (0, import_utils2.getStringLength)(getModelValue());
  449. return (0, import_vue.createVNode)("div", {
  450. "class": bem("word-limit")
  451. }, [(0, import_vue.createVNode)("span", {
  452. "class": bem("word-num")
  453. }, [count]), (0, import_vue.createTextVNode)("/"), props.maxlength]);
  454. }
  455. };
  456. const renderMessage = () => {
  457. if (form && form.props.showErrorMessage === false) {
  458. return;
  459. }
  460. const message = props.errorMessage || state.validateMessage;
  461. if (message) {
  462. const slot = slots["error-message"];
  463. const errorMessageAlign = getProp("errorMessageAlign");
  464. return (0, import_vue.createVNode)("div", {
  465. "class": bem("error-message", errorMessageAlign)
  466. }, [slot ? slot({
  467. message
  468. }) : message]);
  469. }
  470. };
  471. const renderLabel = () => {
  472. const labelWidth = getProp("labelWidth");
  473. const labelAlign = getProp("labelAlign");
  474. const colon = getProp("colon") ? ":" : "";
  475. if (slots.label) {
  476. return [slots.label(), colon];
  477. }
  478. if (props.label) {
  479. return (0, import_vue.createVNode)("label", {
  480. "id": `${id}-label`,
  481. "for": slots.input ? void 0 : getInputId(),
  482. "data-allow-mismatch": "attribute",
  483. "onClick": (event) => {
  484. (0, import_utils.preventDefault)(event);
  485. focus();
  486. },
  487. "style": labelAlign === "top" && labelWidth ? {
  488. width: (0, import_utils.addUnit)(labelWidth)
  489. } : void 0
  490. }, [props.label + colon]);
  491. }
  492. };
  493. const renderFieldBody = () => [(0, import_vue.createVNode)("div", {
  494. "class": bem("body")
  495. }, [renderInput(), showClear.value && (0, import_vue.createVNode)(import_icon.Icon, {
  496. "ref": clearIconRef,
  497. "name": props.clearIcon,
  498. "class": bem("clear")
  499. }, null), renderRightIcon(), slots.button && (0, import_vue.createVNode)("div", {
  500. "class": bem("button")
  501. }, [slots.button()])]), renderWordLimit(), renderMessage()];
  502. (0, import_use_expose.useExpose)({
  503. blur,
  504. focus,
  505. validate,
  506. formValue,
  507. resetValidation,
  508. getValidationStatus
  509. });
  510. (0, import_vue.provide)(import_use.CUSTOM_FIELD_INJECTION_KEY, {
  511. customValue,
  512. resetValidation,
  513. validateWithTrigger
  514. });
  515. (0, import_vue.watch)(() => props.modelValue, () => {
  516. updateValue(getModelValue());
  517. resetValidation();
  518. validateWithTrigger("onChange");
  519. (0, import_vue.nextTick)(adjustTextareaSize);
  520. });
  521. (0, import_vue.onMounted)(() => {
  522. updateValue(getModelValue(), props.formatTrigger);
  523. (0, import_vue.nextTick)(adjustTextareaSize);
  524. });
  525. (0, import_use.useEventListener)("touchstart", onClear, {
  526. target: (0, import_vue.computed)(() => {
  527. var _a;
  528. return (_a = clearIconRef.value) == null ? void 0 : _a.$el;
  529. })
  530. });
  531. return () => {
  532. const disabled = getProp("disabled");
  533. const labelAlign = getProp("labelAlign");
  534. const LeftIcon = renderLeftIcon();
  535. const renderTitle = () => {
  536. const Label = renderLabel();
  537. if (labelAlign === "top") {
  538. return [LeftIcon, Label].filter(Boolean);
  539. }
  540. return Label || [];
  541. };
  542. return (0, import_vue.createVNode)(import_cell.Cell, {
  543. "size": props.size,
  544. "class": bem({
  545. error: showError.value,
  546. disabled,
  547. [`label-${labelAlign}`]: labelAlign
  548. }),
  549. "center": props.center,
  550. "border": props.border,
  551. "isLink": props.isLink,
  552. "clickable": props.clickable,
  553. "titleStyle": labelStyle.value,
  554. "valueClass": bem("value"),
  555. "titleClass": [bem("label", [labelAlign, {
  556. required: showRequiredMark.value
  557. }]), props.labelClass],
  558. "arrowDirection": props.arrowDirection
  559. }, {
  560. icon: LeftIcon && labelAlign !== "top" ? () => LeftIcon : null,
  561. title: renderTitle,
  562. value: renderFieldBody,
  563. extra: slots.extra
  564. });
  565. };
  566. }
  567. });