nuxt-ui-tools 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/form/components/root/form.vue +1 -1
  3. package/dist/runtime/form/composables/use-form-validation.d.ts +2 -3
  4. package/dist/runtime/form/composables/use-form-validation.js +24 -112
  5. package/dist/runtime/form/composables/use-form.js +1 -1
  6. package/dist/runtime/form/fields/password/component.vue +132 -28
  7. package/dist/runtime/form/fields/password/types.d.ts +6 -0
  8. package/dist/runtime/form/fields/text/component.vue +7 -2
  9. package/dist/runtime/form/types/field.d.ts +1 -1
  10. package/dist/runtime/form/types/validation.d.ts +3 -20
  11. package/dist/runtime/form/utils/state.d.ts +1 -14
  12. package/dist/runtime/form/utils/state.js +0 -199
  13. package/dist/runtime/i18n/locales/en.js +1 -0
  14. package/dist/runtime/i18n/locales/fr.js +1 -0
  15. package/dist/runtime/i18n/types.d.ts +1 -0
  16. package/dist/runtime/table/components/data-list/data-list-content.d.vue.ts +19 -17
  17. package/dist/runtime/table/components/data-list/data-list-content.vue +14 -87
  18. package/dist/runtime/table/components/data-list/data-list-content.vue.d.ts +19 -17
  19. package/dist/runtime/table/components/data-list/data-list-default.d.vue.ts +1 -4
  20. package/dist/runtime/table/components/data-list/data-list-default.vue.d.ts +1 -4
  21. package/dist/runtime/table/components/data-list/data-list-error-state.d.vue.ts +13 -0
  22. package/dist/runtime/table/components/data-list/data-list-error-state.vue +91 -0
  23. package/dist/runtime/table/components/data-list/data-list-error-state.vue.d.ts +13 -0
  24. package/dist/runtime/table/components/data-list/data-list-layout-switch.vue +1 -1
  25. package/dist/runtime/table/components/data-list/data-list-refresh.vue +42 -4
  26. package/dist/runtime/table/components/data-list/data-list-root.d.vue.ts +1 -9
  27. package/dist/runtime/table/components/data-list/data-list-root.vue.d.ts +1 -9
  28. package/dist/runtime/table/components/data-list/data-list-sort-menu.vue +11 -5
  29. package/dist/runtime/table/components/data-list.d.vue.ts +2 -13
  30. package/dist/runtime/table/components/data-list.vue.d.ts +2 -13
  31. package/dist/runtime/table/components/drawers/column-panel.vue +7 -2
  32. package/dist/runtime/table/components/filters/panel/filter-panel-chips.vue +2 -5
  33. package/dist/runtime/table/components/filters/panel/option-filter-panel-field.vue +1 -1
  34. package/dist/runtime/table/components/filters/shared/dynamic-filter-picker.d.vue.ts +2 -2
  35. package/dist/runtime/table/components/filters/shared/dynamic-filter-picker.vue +103 -101
  36. package/dist/runtime/table/components/filters/shared/dynamic-filter-picker.vue.d.ts +2 -2
  37. package/dist/runtime/table/components/filters/shared/filter-option-multiple-list.vue +2 -2
  38. package/dist/runtime/table/components/filters/shared/filter-option-picker-content.vue +3 -3
  39. package/dist/runtime/table/components/filters/shared/filter-option-single-list.vue +1 -1
  40. package/dist/runtime/table/components/filters/shared/filter-popover-shell.d.vue.ts +3 -3
  41. package/dist/runtime/table/components/filters/shared/filter-popover-shell.vue +17 -15
  42. package/dist/runtime/table/components/filters/shared/filter-popover-shell.vue.d.ts +3 -3
  43. package/dist/runtime/table/components/filters/shared/filter-searchable-panel.vue +10 -2
  44. package/dist/runtime/table/components/filters/sheet/filter-sheet.vue +4 -3
  45. package/dist/runtime/table/components/filters/tags/filter-tags-bar.vue +1 -1
  46. package/dist/runtime/table/components/grid/grid-renderer.vue +2 -4
  47. package/dist/runtime/table/components/table/table-renderer.d.vue.ts +8 -2
  48. package/dist/runtime/table/components/table/table-renderer.vue +24 -6
  49. package/dist/runtime/table/components/table/table-renderer.vue.d.ts +8 -2
  50. package/dist/runtime/table/composables/use-option-filter-editor-state.d.ts +1 -0
  51. package/dist/runtime/table/composables/use-option-filter-editor-state.js +2 -2
  52. package/dist/runtime/table/composables/use-table-actions.js +10 -5
  53. package/dist/runtime/table/composables/use-table-api.js +4 -1
  54. package/dist/runtime/table/composables/use-table-columns.d.ts +6 -2
  55. package/dist/runtime/table/composables/use-table-data.d.ts +2 -1
  56. package/dist/runtime/table/composables/use-table-data.js +19 -2
  57. package/dist/runtime/table/composables/use-table-filter-options.d.ts +1 -0
  58. package/dist/runtime/table/composables/use-table-filter-options.js +2 -1
  59. package/dist/runtime/table/composables/use-table-internals.d.ts +18 -6
  60. package/dist/runtime/table/types/actions.d.ts +3 -2
  61. package/dist/runtime/table/types/data-list.d.ts +8 -0
  62. package/dist/runtime/table/types/source.d.ts +10 -1
  63. package/dist/runtime/table/utils/builders/table-source.d.ts +14 -15
  64. package/dist/runtime/table/utils/client-query.d.ts +1 -1
  65. package/dist/runtime/table/utils/columns/render.d.ts +13 -5
  66. package/dist/runtime/table/utils/columns/render.js +3 -2
  67. package/dist/runtime/table/utils/filters/editor-size.js +9 -9
  68. package/dist/runtime/table/utils/filters/options.js +6 -1
  69. package/dist/runtime/table/utils/index.d.ts +1 -0
  70. package/dist/runtime/table/utils/index.js +1 -0
  71. package/dist/runtime/table/utils/prefetch.js +5 -1
  72. package/dist/runtime/table/utils/source-request.d.ts +2 -0
  73. package/dist/runtime/table/utils/source-request.js +9 -0
  74. package/package.json +1 -1
package/dist/module.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "configKey": "nuxtUiTools",
6
6
  "docs": "https://ui.nuxt.com/docs/getting-started/installation/nuxt",
7
7
  "name": "nuxt-ui-tools",
8
- "version": "1.0.0",
8
+ "version": "1.0.1",
9
9
  "builder": {
10
10
  "@nuxt/module-builder": "1.0.3",
11
11
  "unbuild": "unknown"
@@ -293,7 +293,7 @@ function getSchemaSyncInput(schema) {
293
293
  function getSchemaValidationMode(schema) {
294
294
  const controls = getSchemaControls(schema);
295
295
  const value = controls ? Object.getOwnPropertyDescriptor(controls, "validate")?.value : void 0;
296
- return value === false || value === "required" || value === "rules" ? value : true;
296
+ return value === false || value === "required" || value === "validators" ? value : true;
297
297
  }
298
298
  function getSchemaControls(schema) {
299
299
  if (!isRecord(schema)) {
@@ -3,9 +3,8 @@ import type { FormFieldApiFactory } from './use-form-state.js';
3
3
  /**
4
4
  * Connects schema-owned field rules to Regle's validation tree.
5
5
  *
6
- * Regle owns validation execution, dirty state, async completion, and error collection. The
7
- * package-specific callback rules are converted to native Regle rules at the schema boundary,
8
- * so renderers only consume the resulting field errors.
6
+ * Regle owns validation execution, dirty state, async completion, and error collection, so
7
+ * renderers only consume the resulting field errors.
9
8
  */
10
9
  export declare function useFormValidation(params: {
11
10
  schema: () => FormValue;
@@ -1,18 +1,11 @@
1
1
  import { useRegle } from "@regle/core";
2
- import { withAsync, withMessage } from "@regle/rules";
2
+ import { withMessage } from "@regle/rules";
3
3
  import { computed, nextTick, reactive, ref, unref, watch } from "vue";
4
4
  import { calendarSeedFromValue, isDateFamilyRange } from "../fields/date-family/utils.js";
5
5
  import { resolveFieldDependencies } from "../utils/dependencies.js";
6
6
  import { createFormFieldInstance } from "../utils/field-instance.js";
7
7
  import { getPathValue, isRecord, pathSegments } from "../utils/path.js";
8
- import {
9
- isBoolean,
10
- isFunction,
11
- isNumber,
12
- isObject,
13
- isString,
14
- isUndefined
15
- } from "../utils/predicate.js";
8
+ import { isBoolean, isFunction, isNumber, isObject, isString } from "../utils/predicate.js";
16
9
  import {
17
10
  fieldPath,
18
11
  getArrayItemFields,
@@ -27,12 +20,11 @@ import {
27
20
  resolveRequiredMessage,
28
21
  shouldRenderField
29
22
  } from "../utils/state.js";
30
- import { resolveFormBoundaryText } from "../utils/text.js";
23
+ import { resolveFormBoundaryText, resolveFormText } from "../utils/text.js";
31
24
  export function useFormValidation(params) {
32
25
  const customErrors = ref([]);
33
26
  const touchedPaths = ref([]);
34
27
  const pendingPaths = ref(/* @__PURE__ */ new Map());
35
- const dynamicMessages = ref(/* @__PURE__ */ new Map());
36
28
  const validatedMessages = ref(/* @__PURE__ */ new Map());
37
29
  const validationRuns = /* @__PURE__ */ new Map();
38
30
  const regle = useRegle(
@@ -40,7 +32,6 @@ export function useFormValidation(params) {
40
32
  () => buildRegleRules({
41
33
  apiFactory: params.apiFactory,
42
34
  context: params.context,
43
- dynamicMessages,
44
35
  getDateMaxMessage: params.getDateMaxMessage,
45
36
  getDateMinMessage: params.getDateMinMessage,
46
37
  getRequiredMessage: params.getRequiredMessage,
@@ -57,7 +48,6 @@ export function useFormValidation(params) {
57
48
  () => buildRegleRules({
58
49
  apiFactory: params.apiFactory,
59
50
  context: params.context,
60
- dynamicMessages,
61
51
  getDateMaxMessage: params.getDateMaxMessage,
62
52
  getDateMinMessage: params.getDateMinMessage,
63
53
  getRequiredMessage: params.getRequiredMessage,
@@ -75,7 +65,6 @@ export function useFormValidation(params) {
75
65
  () => buildPrimitiveItemRules({
76
66
  apiFactory: params.apiFactory,
77
67
  context: params.context,
78
- dynamicMessages,
79
68
  getDateMaxMessage: params.getDateMaxMessage,
80
69
  getDateMinMessage: params.getDateMinMessage,
81
70
  getRequiredMessage: params.getRequiredMessage,
@@ -92,7 +81,6 @@ export function useFormValidation(params) {
92
81
  () => buildPrimitiveItemRules({
93
82
  apiFactory: params.apiFactory,
94
83
  context: params.context,
95
- dynamicMessages,
96
84
  getDateMaxMessage: params.getDateMaxMessage,
97
85
  getDateMinMessage: params.getDateMinMessage,
98
86
  getRequiredMessage: params.getRequiredMessage,
@@ -164,7 +152,6 @@ export function useFormValidation(params) {
164
152
  }
165
153
  resetRegleRoots();
166
154
  const paths = collectFormFieldsPathsForSchema(params.schema(), params.state);
167
- clearDynamicMessages(paths);
168
155
  clearValidatedMessages(paths);
169
156
  const prepared = prepareReglePaths(
170
157
  { items: itemsRegle.r$, main: regle.r$ },
@@ -214,7 +201,6 @@ export function useFormValidation(params) {
214
201
  if (params.getValidationMode() === false) {
215
202
  return true;
216
203
  }
217
- clearDynamicMessages(paths);
218
204
  clearValidatedMessages(paths);
219
205
  const prepared = prepareReglePaths(
220
206
  { items: itemsRegle.r$, main: regle.r$ },
@@ -282,7 +268,6 @@ export function useFormValidation(params) {
282
268
  if (!path) {
283
269
  customErrors.value = [];
284
270
  touchedPaths.value = [];
285
- dynamicMessages.value = /* @__PURE__ */ new Map();
286
271
  validatedMessages.value = /* @__PURE__ */ new Map();
287
272
  resetRegleRoots();
288
273
  return;
@@ -294,11 +279,6 @@ export function useFormValidation(params) {
294
279
  touchedPaths.value = touchedPaths.value.filter(
295
280
  (touchedPath) => touchedPath !== key && !touchedPath.startsWith(`${key}.`)
296
281
  );
297
- dynamicMessages.value = new Map(
298
- [...dynamicMessages.value.entries()].filter(
299
- ([messageKey]) => !messageKey.startsWith(`${key}:`)
300
- )
301
- );
302
282
  validatedMessages.value = new Map(
303
283
  [...validatedMessages.value.entries()].filter(
304
284
  ([messagePath]) => messagePath !== key && !messagePath.startsWith(`${key}.`)
@@ -308,17 +288,9 @@ export function useFormValidation(params) {
308
288
  function clearValidationState() {
309
289
  customErrors.value = [];
310
290
  touchedPaths.value = [];
311
- dynamicMessages.value = /* @__PURE__ */ new Map();
312
291
  validatedMessages.value = /* @__PURE__ */ new Map();
313
292
  resetRegleRoots();
314
293
  }
315
- function clearDynamicMessages(paths) {
316
- dynamicMessages.value = new Map(
317
- [...dynamicMessages.value.entries()].filter(
318
- ([messageKey]) => !paths.some((path) => messageKey.startsWith(`${path}:`))
319
- )
320
- );
321
- }
322
294
  function clearValidatedMessages(paths) {
323
295
  if (!paths.length) {
324
296
  return;
@@ -500,9 +472,7 @@ function isRegleRuleTreeNode(value) {
500
472
  }
501
473
  function buildLeafRules(params) {
502
474
  const output = {};
503
- const validation = Object.getOwnPropertyDescriptor(params.field, "validation")?.value;
504
- const key = params.path.join(".");
505
- if (params.mode !== "rules" && resolveRequired(params.field, params.callbackParams)) {
475
+ if (params.mode !== "validators" && resolveRequired(params.field, params.callbackParams)) {
506
476
  output.required = withMessage(
507
477
  (value) => !isEmptyValue(value),
508
478
  resolveRequiredMessage(params.field, params.getRequiredMessage?.())
@@ -512,6 +482,7 @@ function buildLeafRules(params) {
512
482
  return output;
513
483
  }
514
484
  applyDateBoundsRules(output, params);
485
+ applyPasswordRequirementRules(output, params);
515
486
  const authoredValidators = "validators" in params.field ? params.field.validators : void 0;
516
487
  let validators;
517
488
  if (isValidatorMap(authoredValidators)) {
@@ -522,96 +493,37 @@ function buildLeafRules(params) {
522
493
  if (validators) {
523
494
  for (const name in validators) {
524
495
  const rule = validators[name];
525
- if (rule) {
496
+ if (rule && (params.includeAsync || !isAsyncValidator(rule))) {
526
497
  output[name] = rule;
527
498
  }
528
499
  }
529
500
  }
530
- const authoredRules = isRecord(validation) ? Object.getOwnPropertyDescriptor(validation, "rules")?.value : void 0;
531
- if (!Array.isArray(authoredRules)) {
532
- return output;
533
- }
534
- authoredRules.forEach((rule, index) => {
535
- if (!isRecord(rule)) {
536
- return;
537
- }
538
- const validate = Object.getOwnPropertyDescriptor(rule, "validate")?.value;
539
- if (!isFunction(validate)) {
540
- return;
541
- }
542
- if (!params.includeAsync && isAsyncFunction(validate)) {
543
- return;
544
- }
545
- const ruleName = resolveRuleName(rule, index);
546
- const messageKey = `${key}:${ruleName}`;
547
- function message() {
548
- return params.dynamicMessages.value.get(messageKey) ?? resolveRuleMessage(rule, params.field);
549
- }
550
- if (isAsyncFunction(validate)) {
551
- output[ruleName] = withMessage(
552
- withAsync(
553
- async () => resolveRuleResult(
554
- await validate({ ...params.callbackParams, api: params.callbackParams.api }),
555
- messageKey,
556
- params.dynamicMessages
557
- )
558
- ),
559
- message
560
- );
561
- } else {
562
- output[ruleName] = withMessage(
563
- () => resolveRuleResult(
564
- validate({ ...params.callbackParams, api: params.callbackParams.api }),
565
- messageKey,
566
- params.dynamicMessages
567
- ),
568
- message
569
- );
570
- }
571
- });
572
501
  return output;
573
502
  }
574
- function isValidatorMap(value) {
575
- return isObject(value);
576
- }
577
- function resolveRuleResult(result, messageKey, dynamicMessages) {
578
- if (isString(result)) {
579
- setDynamicMessage(dynamicMessages, messageKey, result);
580
- return false;
503
+ function applyPasswordRequirementRules(output, params) {
504
+ if (params.field.type !== "password") {
505
+ return;
581
506
  }
582
- clearDynamicMessage(dynamicMessages, messageKey);
583
- return result === true || result === null || isUndefined(result);
584
- }
585
- function isAsyncFunction(value) {
586
- return Object.getPrototypeOf(value)?.constructor?.name === "AsyncFunction";
587
- }
588
- function setDynamicMessage(messages, key, value) {
589
- const next = new Map(messages.value);
590
- next.set(key, value);
591
- messages.value = next;
592
- }
593
- function clearDynamicMessage(messages, key) {
594
- if (!messages.value.has(key)) {
507
+ const authoredProps = Object.getOwnPropertyDescriptor(params.field, "props")?.value;
508
+ const resolvedProps = isFunction(authoredProps) ? authoredProps(params.callbackParams) : authoredProps;
509
+ if (!isObject(resolvedProps) || !Array.isArray(resolvedProps.requirements)) {
595
510
  return;
596
511
  }
597
- const next = new Map(messages.value);
598
- next.delete(key);
599
- messages.value = next;
512
+ for (const requirement of resolvedProps.requirements) {
513
+ output[`requirement:${requirement.key}`] = withMessage(
514
+ (value) => isEmptyValue(value) || isString(value) && requirement.validate(value),
515
+ resolveFormText(requirement.label) ?? requirement.key
516
+ );
517
+ }
600
518
  }
601
- function resolveRuleName(rule, index) {
602
- const name = Object.getOwnPropertyDescriptor(rule, "name")?.value;
603
- return isString(name) && name.length ? name : `rule-${index + 1}`;
519
+ function isValidatorMap(value) {
520
+ return isObject(value);
604
521
  }
605
- function resolveRuleMessage(rule, field) {
606
- const message = Object.getOwnPropertyDescriptor(rule, "message")?.value;
607
- if (isFunction(message)) {
608
- return String(message());
609
- }
610
- if (isString(message) || isNumber(message)) {
611
- return String(message);
522
+ function isAsyncValidator(rule) {
523
+ if (isObject(rule)) {
524
+ return rule._async === true;
612
525
  }
613
- const name = Object.getOwnPropertyDescriptor(rule, "name")?.value;
614
- return isString(name) ? `Invalid value for ${name}.` : `Invalid value for ${field.key}.`;
526
+ return isFunction(rule) && Object.getPrototypeOf(rule)?.constructor?.name === "AsyncFunction";
615
527
  }
616
528
  function resolveRegleStatusErrors(status) {
617
529
  const errors = readReactiveProperty(status, "$errors");
@@ -162,7 +162,7 @@ function getSchemaSyncInput(schema) {
162
162
  function getSchemaValidationMode(schema) {
163
163
  const controls = getSchemaControls(schema);
164
164
  const value = controls ? Object.getOwnPropertyDescriptor(controls, "validate")?.value : void 0;
165
- return value === false || value === "required" || value === "rules" ? value : true;
165
+ return value === false || value === "required" || value === "validators" ? value : true;
166
166
  }
167
167
  function getSchemaControls(schema) {
168
168
  if (!isRecord(schema)) {
@@ -1,6 +1,9 @@
1
1
  <script setup>
2
2
  import UButton from "@nuxt/ui/components/Button.vue";
3
+ import UIcon from "@nuxt/ui/components/Icon.vue";
3
4
  import UInput from "@nuxt/ui/components/Input.vue";
5
+ import UPopover from "@nuxt/ui/components/Popover.vue";
6
+ import UProgress from "@nuxt/ui/components/Progress.vue";
4
7
  import { computed, ref } from "vue";
5
8
  import { useUiToolsLocale } from "../../../i18n/use-locale";
6
9
  import FormFieldShell from "../../components/renderer/form-field-shell.vue";
@@ -16,14 +19,29 @@ const { t } = useUiToolsLocale();
16
19
  const { fieldProps, form, controlProps, controlSize, disabled, handleBlur, placeholder } = useFieldControl(
17
20
  () => props.field,
18
21
  () => props.path,
19
- { omit: ["visibilityToggle"] }
22
+ { omit: ["requirements", "visibilityToggle"] }
20
23
  );
21
24
  const visible = ref(false);
25
+ const requirementsOpen = ref(false);
22
26
  const visibilityToggleEnabled = computed(() => fieldProps.value.visibilityToggle !== false);
23
27
  const visibilityToggleConfig = computed(
24
28
  () => isPasswordVisibilityConfig(fieldProps.value.visibilityToggle) ? fieldProps.value.visibilityToggle : void 0
25
29
  );
26
30
  const inputType = computed(() => visible.value ? "text" : "password");
31
+ const requirements = computed(
32
+ () => Array.isArray(fieldProps.value.requirements) ? fieldProps.value.requirements : []
33
+ );
34
+ const requirementResults = computed(() => {
35
+ const value = model.value ?? "";
36
+ return requirements.value.map((requirement) => ({
37
+ key: requirement.key,
38
+ label: resolveFormText(requirement.label) ?? requirement.key,
39
+ passed: requirement.validate(value)
40
+ }));
41
+ });
42
+ const passedRequirementCount = computed(
43
+ () => requirementResults.value.filter((requirement) => requirement.passed).length
44
+ );
27
45
  const visibilityLabel = computed(() => {
28
46
  const configured = visible.value ? visibilityToggleConfig.value?.hideLabel : visibilityToggleConfig.value?.showLabel;
29
47
  return resolveFormText(configured) ?? t(visible.value ? "form.fields.password.hide" : "form.fields.password.show");
@@ -34,6 +52,10 @@ const visibilityIcon = computed(
34
52
  function isPasswordVisibilityConfig(value) {
35
53
  return isObject(value);
36
54
  }
55
+ function handleInputBlur(event) {
56
+ requirementsOpen.value = false;
57
+ handleBlur(event);
58
+ }
37
59
  const model = computed({
38
60
  get: () => {
39
61
  const value = form.getValue(props.path);
@@ -44,34 +66,12 @@ const model = computed({
44
66
  </script>
45
67
 
46
68
  <template>
47
- <UInput
69
+ <UPopover
48
70
  v-if="bare"
49
- v-model="model"
50
- v-bind="controlProps"
51
- class="w-full"
52
- :type="inputType"
53
- :placeholder="placeholder"
54
- :disabled="disabled"
55
- @blur="handleBlur"
71
+ :open="requirementsOpen && requirements.length > 0"
72
+ :content="{ align: 'start', side: 'bottom', sideOffset: 8 }"
73
+ :ui="{ content: 'w-[min(22rem,calc(100vw-2rem))] p-3.5' }"
56
74
  >
57
- <template v-if="visibilityToggleEnabled" #trailing>
58
- <UButton
59
- type="button"
60
- color="neutral"
61
- variant="link"
62
- :size="controlSize"
63
- :icon="visibilityIcon"
64
- :aria-label="visibilityLabel"
65
- :title="visibilityLabel"
66
- :aria-pressed="visible"
67
- :disabled="disabled"
68
- :ui="{ base: 'p-0' }"
69
- @mousedown.prevent
70
- @click="visible = !visible"
71
- />
72
- </template>
73
- </UInput>
74
- <FormFieldShell v-else :field="field" :path="path">
75
75
  <UInput
76
76
  v-model="model"
77
77
  v-bind="controlProps"
@@ -79,7 +79,8 @@ const model = computed({
79
79
  :type="inputType"
80
80
  :placeholder="placeholder"
81
81
  :disabled="disabled"
82
- @blur="handleBlur"
82
+ @focus="requirementsOpen = true"
83
+ @blur="handleInputBlur"
83
84
  >
84
85
  <template v-if="visibilityToggleEnabled" #trailing>
85
86
  <UButton
@@ -98,5 +99,108 @@ const model = computed({
98
99
  />
99
100
  </template>
100
101
  </UInput>
102
+
103
+ <template #content>
104
+ <div class="space-y-3" data-form-password-requirements="">
105
+ <div class="flex items-center justify-between gap-3 text-xs font-medium text-toned">
106
+ <span>{{ t("form.fields.password.requirements") }}</span>
107
+ <span class="tabular-nums">
108
+ {{ passedRequirementCount }}/{{ requirementResults.length }}
109
+ </span>
110
+ </div>
111
+ <UProgress
112
+ :model-value="passedRequirementCount"
113
+ :max="requirementResults.length"
114
+ size="sm"
115
+ color="success"
116
+ data-form-password-progress=""
117
+ :data-value="passedRequirementCount"
118
+ :data-max="requirementResults.length"
119
+ />
120
+ <ul class="space-y-1.5">
121
+ <li
122
+ v-for="requirement in requirementResults"
123
+ :key="requirement.key"
124
+ class="flex items-start gap-2 text-xs"
125
+ :class="requirement.passed ? 'text-success' : 'text-muted'"
126
+ >
127
+ <UIcon
128
+ :name="requirement.passed ? 'i-lucide-check' : 'i-lucide-x'"
129
+ class="mt-0.5 size-3.5 shrink-0"
130
+ />
131
+ <span>{{ requirement.label }}</span>
132
+ </li>
133
+ </ul>
134
+ </div>
135
+ </template>
136
+ </UPopover>
137
+ <FormFieldShell v-else :field="field" :path="path">
138
+ <UPopover
139
+ :open="requirementsOpen && requirements.length > 0"
140
+ :content="{ align: 'start', side: 'bottom', sideOffset: 8 }"
141
+ :ui="{ content: 'w-[min(22rem,calc(100vw-2rem))] p-3.5' }"
142
+ >
143
+ <UInput
144
+ v-model="model"
145
+ v-bind="controlProps"
146
+ class="w-full"
147
+ :type="inputType"
148
+ :placeholder="placeholder"
149
+ :disabled="disabled"
150
+ @focus="requirementsOpen = true"
151
+ @blur="handleInputBlur"
152
+ >
153
+ <template v-if="visibilityToggleEnabled" #trailing>
154
+ <UButton
155
+ type="button"
156
+ color="neutral"
157
+ variant="link"
158
+ :size="controlSize"
159
+ :icon="visibilityIcon"
160
+ :aria-label="visibilityLabel"
161
+ :title="visibilityLabel"
162
+ :aria-pressed="visible"
163
+ :disabled="disabled"
164
+ :ui="{ base: 'p-0' }"
165
+ @mousedown.prevent
166
+ @click="visible = !visible"
167
+ />
168
+ </template>
169
+ </UInput>
170
+
171
+ <template #content>
172
+ <div class="space-y-3" data-form-password-requirements="">
173
+ <div class="flex items-center justify-between gap-3 text-xs font-medium text-toned">
174
+ <span>{{ t("form.fields.password.requirements") }}</span>
175
+ <span class="tabular-nums">
176
+ {{ passedRequirementCount }}/{{ requirementResults.length }}
177
+ </span>
178
+ </div>
179
+ <UProgress
180
+ :model-value="passedRequirementCount"
181
+ :max="requirementResults.length"
182
+ size="sm"
183
+ color="success"
184
+ data-form-password-progress=""
185
+ :data-value="passedRequirementCount"
186
+ :data-max="requirementResults.length"
187
+ />
188
+ <ul class="space-y-1.5">
189
+ <li
190
+ v-for="requirement in requirementResults"
191
+ :key="requirement.key"
192
+ class="flex items-start gap-2 text-xs"
193
+ :class="requirement.passed ? 'text-success' : 'text-muted'"
194
+ >
195
+ <UIcon
196
+ :name="requirement.passed ? 'i-lucide-check' : 'i-lucide-x'"
197
+ class="mt-0.5 size-3.5 shrink-0"
198
+ />
199
+ <span>{{ requirement.label }}</span>
200
+ </li>
201
+ </ul>
202
+ </div>
203
+ </template>
204
+ </UPopover>
101
205
  </FormFieldShell>
102
206
  </template>
@@ -7,7 +7,13 @@ export interface FormPasswordVisibilityToggle {
7
7
  showLabel?: FormText;
8
8
  hideLabel?: FormText;
9
9
  }
10
+ export interface FormPasswordRequirement {
11
+ key: string;
12
+ label: FormText;
13
+ validate: (value: string) => boolean;
14
+ }
10
15
  export interface FormPasswordProps {
16
+ requirements?: readonly FormPasswordRequirement[];
11
17
  visibilityToggle?: boolean | FormPasswordVisibilityToggle;
12
18
  }
13
19
  export type FormPasswordField<TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>> = FormStatefulFieldBase<'password', string | null, TContext, TDeps, FormPasswordProps>;
@@ -26,6 +26,11 @@ const model = computed({
26
26
  set: (value) => form.setValue(props.path, normalize(value))
27
27
  });
28
28
  const maskOptions = computed(() => maskDirectiveOptions(fieldProps.value.mask));
29
+ const vOptionalMaska = (element, binding, vnode, previousVnode) => {
30
+ if (binding.value !== void 0 && typeof vMaska === "function") {
31
+ vMaska(element, binding, vnode, previousVnode);
32
+ }
33
+ };
29
34
  const prefix = computed(() => resolveFormText(fieldProps.value.prefix));
30
35
  const suffix = computed(() => resolveFormText(fieldProps.value.suffix));
31
36
  const showClear = computed(
@@ -66,7 +71,7 @@ function clear() {
66
71
  <UInput
67
72
  v-if="bare"
68
73
  v-model="model"
69
- v-maska="maskOptions"
74
+ v-optional-maska="maskOptions"
70
75
  v-bind="controlProps"
71
76
  :class="controlClass"
72
77
  :type="fieldProps.inputType ?? 'text'"
@@ -100,7 +105,7 @@ function clear() {
100
105
  <FormFieldShell v-else :field="field" :path="path">
101
106
  <UInput
102
107
  v-model="model"
103
- v-maska="maskOptions"
108
+ v-optional-maska="maskOptions"
104
109
  v-bind="controlProps"
105
110
  :class="controlClass"
106
111
  :type="fieldProps.inputType ?? 'text'"
@@ -72,7 +72,7 @@ export type { FormMatrixField, FormMatrixRow } from '../fields/matrix/types.js';
72
72
  export type { FormNumberField } from '../fields/number/types.js';
73
73
  export type { FormOneTimeCodeField } from '../fields/one-time-code/types.js';
74
74
  export type { FormObjectField } from '../fields/object/types.js';
75
- export type { FormPasswordField } from '../fields/password/types.js';
75
+ export type * from '../fields/password/types';
76
76
  export type { FormPhoneNumberField } from '../fields/phone-number/types.js';
77
77
  export type { FormRadioCardField } from '../fields/radio-card/types.js';
78
78
  export type { FormRadioField } from '../fields/radio/types.js';
@@ -1,22 +1,7 @@
1
1
  import type { RegleRuleRaw } from '@regle/core';
2
2
  import type { FormValue } from './/index.js';
3
3
  import type { FormFieldCallback } from './callbacks.js';
4
- import type { FormMaybePromise, FormObject, FormText } from './utils.js';
5
- /**
6
- * Result returned by a form validation rule.
7
- */
8
- export type FormValidationResult = boolean | string | null | undefined;
9
- /**
10
- * A single validation rule.
11
- */
12
- export interface FormValidationRule<TValue = FormValue, TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>> {
13
- /** Stable rule name used for debugging, i18n, and external error mapping. */
14
- name: string;
15
- /** Returns true for valid values, false/string for invalid values. */
16
- validate: FormFieldCallback<FormMaybePromise<FormValidationResult>, TContext, TDeps, TValue>;
17
- /** Default message used when `validate` returns false. */
18
- message?: FormText;
19
- }
4
+ import type { FormObject, FormText } from './utils.js';
20
5
  /**
21
6
  * Controls when a field starts running validation while the user edits it.
22
7
  *
@@ -26,8 +11,8 @@ export interface FormValidationRule<TValue = FormValue, TContext = NonNullable<u
26
11
  * - `submit`: only validates through explicit form/step submit validation.
27
12
  */
28
13
  export type FormValidationTrigger = 'blur' | 'input' | 'submit';
29
- /** Selects which authored validation layers are evaluated. */
30
- export type FormValidationMode = boolean | 'required' | 'rules';
14
+ /** Selects which native Regle validation layers are evaluated. */
15
+ export type FormValidationMode = boolean | 'required' | 'validators';
31
16
  /**
32
17
  * Concrete validation error stored by the form runtime.
33
18
  */
@@ -63,8 +48,6 @@ export interface FormValidationConfig<TValue = FormValue, TContext = NonNullable
63
48
  required?: boolean | FormFieldCallback<boolean, TContext, TDeps, TValue>;
64
49
  /** Message used by the default required rule. */
65
50
  requiredMessage?: FormText;
66
- /** Additional field rules. */
67
- rules?: readonly FormValidationRule<TValue, TContext, TDeps>[];
68
51
  }
69
52
  /** Native Regle rules keyed by their validation name. */
70
53
  export type FormValidators = Record<string, RegleRuleRaw>;
@@ -1,8 +1,4 @@
1
- import type { FormValue, FormContextData, FormField, FormFieldApi, FormFieldCallbackParams, FormLayoutConfig, FormObject, FormText, FormValidationMode } from '../types/index.js';
2
- export interface FormSubmitError {
3
- path: string;
4
- message: string;
5
- }
1
+ import type { FormValue, FormContextData, FormField, FormFieldApi, FormFieldCallbackParams, FormLayoutConfig, FormObject, FormText } from '../types/index.js';
6
2
  export interface RuntimeFormStep {
7
3
  key?: string;
8
4
  title?: FormText;
@@ -18,17 +14,8 @@ export type InitialStateApiFactory = (state: FormObject, initial: FormObject) =>
18
14
  export declare function buildInitialFormState(schema: FormValue, ctx: FormContextData, input?: FormObject, createApiFactory?: InitialStateApiFactory): import("../../shared/index.js").GenericObject;
19
15
  export declare function buildInitialFormFieldsState(fields: readonly FormField[], ctx: FormContextData): import("../../shared/index.js").GenericObject;
20
16
  export declare function buildFormOutput(schema: FormValue, state: FormObject, ctx: FormContextData, apiFactory: FormFieldApiFactory): import("../../shared/index.js").GenericObject;
21
- export declare function validateFormState(schema: FormValue, state: FormObject, ctx: FormContextData, apiFactory: FormFieldApiFactory, mode?: FormValidationMode): Promise<FormSubmitError[]>;
22
17
  export declare function collectFormFieldPaths(schema: FormValue): readonly string[];
23
18
  export declare function collectFormFieldsPaths(fields: readonly FormField[], parentPath: readonly string[]): readonly string[];
24
- export declare function validateFormFields(params: {
25
- fields: readonly FormField[];
26
- state: FormObject;
27
- ctx: FormContextData;
28
- apiFactory: FormFieldApiFactory;
29
- parentPath: readonly string[];
30
- mode?: FormValidationMode;
31
- }): Promise<FormSubmitError[]>;
32
19
  export type FormFieldApiFactory = (path: readonly string[], field?: FormField) => FormFieldApi;
33
20
  export declare function fieldPath(parentPath: readonly string[], field: FormField): string[];
34
21
  export declare function childParentPath(parentPath: readonly string[], field: FormField): readonly string[];