Field.js 16 KB

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