Field.js 18 KB

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