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.
- package/dist/module.json +1 -1
- package/dist/runtime/form/components/root/form.vue +1 -1
- package/dist/runtime/form/composables/use-form-validation.d.ts +2 -3
- package/dist/runtime/form/composables/use-form-validation.js +24 -112
- package/dist/runtime/form/composables/use-form.js +1 -1
- package/dist/runtime/form/fields/password/component.vue +132 -28
- package/dist/runtime/form/fields/password/types.d.ts +6 -0
- package/dist/runtime/form/fields/text/component.vue +7 -2
- package/dist/runtime/form/types/field.d.ts +1 -1
- package/dist/runtime/form/types/validation.d.ts +3 -20
- package/dist/runtime/form/utils/state.d.ts +1 -14
- package/dist/runtime/form/utils/state.js +0 -199
- package/dist/runtime/i18n/locales/en.js +1 -0
- package/dist/runtime/i18n/locales/fr.js +1 -0
- package/dist/runtime/i18n/types.d.ts +1 -0
- package/dist/runtime/table/components/data-list/data-list-content.d.vue.ts +19 -17
- package/dist/runtime/table/components/data-list/data-list-content.vue +14 -87
- package/dist/runtime/table/components/data-list/data-list-content.vue.d.ts +19 -17
- package/dist/runtime/table/components/data-list/data-list-default.d.vue.ts +1 -4
- package/dist/runtime/table/components/data-list/data-list-default.vue.d.ts +1 -4
- package/dist/runtime/table/components/data-list/data-list-error-state.d.vue.ts +13 -0
- package/dist/runtime/table/components/data-list/data-list-error-state.vue +91 -0
- package/dist/runtime/table/components/data-list/data-list-error-state.vue.d.ts +13 -0
- package/dist/runtime/table/components/data-list/data-list-layout-switch.vue +1 -1
- package/dist/runtime/table/components/data-list/data-list-refresh.vue +42 -4
- package/dist/runtime/table/components/data-list/data-list-root.d.vue.ts +1 -9
- package/dist/runtime/table/components/data-list/data-list-root.vue.d.ts +1 -9
- package/dist/runtime/table/components/data-list/data-list-sort-menu.vue +11 -5
- package/dist/runtime/table/components/data-list.d.vue.ts +2 -13
- package/dist/runtime/table/components/data-list.vue.d.ts +2 -13
- package/dist/runtime/table/components/drawers/column-panel.vue +7 -2
- package/dist/runtime/table/components/filters/panel/filter-panel-chips.vue +2 -5
- package/dist/runtime/table/components/filters/panel/option-filter-panel-field.vue +1 -1
- package/dist/runtime/table/components/filters/shared/dynamic-filter-picker.d.vue.ts +2 -2
- package/dist/runtime/table/components/filters/shared/dynamic-filter-picker.vue +103 -101
- package/dist/runtime/table/components/filters/shared/dynamic-filter-picker.vue.d.ts +2 -2
- package/dist/runtime/table/components/filters/shared/filter-option-multiple-list.vue +2 -2
- package/dist/runtime/table/components/filters/shared/filter-option-picker-content.vue +3 -3
- package/dist/runtime/table/components/filters/shared/filter-option-single-list.vue +1 -1
- package/dist/runtime/table/components/filters/shared/filter-popover-shell.d.vue.ts +3 -3
- package/dist/runtime/table/components/filters/shared/filter-popover-shell.vue +17 -15
- package/dist/runtime/table/components/filters/shared/filter-popover-shell.vue.d.ts +3 -3
- package/dist/runtime/table/components/filters/shared/filter-searchable-panel.vue +10 -2
- package/dist/runtime/table/components/filters/sheet/filter-sheet.vue +4 -3
- package/dist/runtime/table/components/filters/tags/filter-tags-bar.vue +1 -1
- package/dist/runtime/table/components/grid/grid-renderer.vue +2 -4
- package/dist/runtime/table/components/table/table-renderer.d.vue.ts +8 -2
- package/dist/runtime/table/components/table/table-renderer.vue +24 -6
- package/dist/runtime/table/components/table/table-renderer.vue.d.ts +8 -2
- package/dist/runtime/table/composables/use-option-filter-editor-state.d.ts +1 -0
- package/dist/runtime/table/composables/use-option-filter-editor-state.js +2 -2
- package/dist/runtime/table/composables/use-table-actions.js +10 -5
- package/dist/runtime/table/composables/use-table-api.js +4 -1
- package/dist/runtime/table/composables/use-table-columns.d.ts +6 -2
- package/dist/runtime/table/composables/use-table-data.d.ts +2 -1
- package/dist/runtime/table/composables/use-table-data.js +19 -2
- package/dist/runtime/table/composables/use-table-filter-options.d.ts +1 -0
- package/dist/runtime/table/composables/use-table-filter-options.js +2 -1
- package/dist/runtime/table/composables/use-table-internals.d.ts +18 -6
- package/dist/runtime/table/types/actions.d.ts +3 -2
- package/dist/runtime/table/types/data-list.d.ts +8 -0
- package/dist/runtime/table/types/source.d.ts +10 -1
- package/dist/runtime/table/utils/builders/table-source.d.ts +14 -15
- package/dist/runtime/table/utils/client-query.d.ts +1 -1
- package/dist/runtime/table/utils/columns/render.d.ts +13 -5
- package/dist/runtime/table/utils/columns/render.js +3 -2
- package/dist/runtime/table/utils/filters/editor-size.js +9 -9
- package/dist/runtime/table/utils/filters/options.js +6 -1
- package/dist/runtime/table/utils/index.d.ts +1 -0
- package/dist/runtime/table/utils/index.js +1 -0
- package/dist/runtime/table/utils/prefetch.js +5 -1
- package/dist/runtime/table/utils/source-request.d.ts +2 -0
- package/dist/runtime/table/utils/source-request.js +9 -0
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -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 === "
|
|
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
|
|
7
|
-
*
|
|
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 {
|
|
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
|
-
|
|
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
|
|
575
|
-
|
|
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
|
-
|
|
583
|
-
|
|
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
|
|
598
|
-
|
|
599
|
-
|
|
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
|
|
602
|
-
|
|
603
|
-
return isString(name) && name.length ? name : `rule-${index + 1}`;
|
|
519
|
+
function isValidatorMap(value) {
|
|
520
|
+
return isObject(value);
|
|
604
521
|
}
|
|
605
|
-
function
|
|
606
|
-
|
|
607
|
-
|
|
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
|
-
|
|
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 === "
|
|
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
|
-
<
|
|
69
|
+
<UPopover
|
|
48
70
|
v-if="bare"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
@
|
|
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
|
|
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 {
|
|
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
|
|
30
|
-
export type FormValidationMode = boolean | 'required' | '
|
|
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
|
|
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[];
|