@milaboratories/uikit 2.15.19 → 2.15.21
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/.turbo/turbo-build.log +15 -15
- package/.turbo/turbo-formatter$colon$check.log +2 -2
- package/.turbo/turbo-linter$colon$check.log +2 -2
- package/.turbo/turbo-types$colon$check.log +1 -1
- package/CHANGELOG.md +24 -0
- package/dist/__tests__/valueless-attribute.test.d.ts +2 -0
- package/dist/__tests__/valueless-attribute.test.d.ts.map +1 -0
- package/dist/components/PlNumberField/PlNumberField.js.map +1 -1
- package/dist/components/PlNumberField/PlNumberField.vue.d.ts +3 -2
- package/dist/components/PlNumberField/PlNumberField.vue.d.ts.map +1 -1
- package/dist/components/PlNumberField/PlNumberField.vue2.js +2 -2
- package/dist/components/PlNumberField/PlNumberField.vue2.js.map +1 -1
- package/dist/components/PlTextField/PlTextField.js.map +1 -1
- package/dist/components/PlTextField/PlTextField.vue.d.ts +3 -2
- package/dist/components/PlTextField/PlTextField.vue.d.ts.map +1 -1
- package/dist/components/PlTextField/PlTextField.vue2.js +2 -2
- package/dist/components/PlTextField/PlTextField.vue2.js.map +1 -1
- package/dist/utils/booleanProps.d.ts +16 -0
- package/dist/utils/booleanProps.d.ts.map +1 -0
- package/package.json +5 -5
- package/src/__tests__/valueless-attribute.test.ts +43 -0
- package/src/components/PlNumberField/PlNumberField.vue +3 -2
- package/src/components/PlTextField/PlTextField.vue +3 -2
- package/src/utils/booleanProps.ts +18 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
WARN Issue while reading "/home/runner/_work/platforma/platforma/.npmrc". Failed to replace env in config: ${NPMJS_TOKEN}
|
|
2
2
|
|
|
3
|
-
> @milaboratories/uikit@2.15.
|
|
3
|
+
> @milaboratories/uikit@2.15.21 build /home/runner/_work/platforma/platforma/lib/ui/uikit
|
|
4
4
|
> ts-builder build --target browser-lib --build-config ./build.browser-lib.config.js
|
|
5
5
|
|
|
6
6
|
Building browser-lib project...
|
|
@@ -24,7 +24,7 @@ Using custom config: ./build.browser-lib.config.js
|
|
|
24
24
|
rendering chunks...
|
|
25
25
|
|
|
26
26
|
[vite:dts] Start generate declaration files...
|
|
27
|
-
[vite:dts] Declaration files built in
|
|
27
|
+
[vite:dts] Declaration files built in 6125ms.
|
|
28
28
|
|
|
29
29
|
computing gzip size...
|
|
30
30
|
dist/components/PlClipboard/PlClipboard.vue?vue&type=style&index=0&lang.css 0.04 kB │ gzip: 0.06 kB
|
|
@@ -216,7 +216,7 @@ dist/components/PlBtnDanger/PlBtnDanger.js
|
|
|
216
216
|
dist/components/PlConfirmDialog.js 0.21 kB │ gzip: 0.16 kB │ map: 1.39 kB
|
|
217
217
|
dist/components/PlFileInput/PlFileInput.js 0.21 kB │ gzip: 0.16 kB │ map: 6.62 kB
|
|
218
218
|
dist/components/PlStatusTag/PlStatusTag.js 0.21 kB │ gzip: 0.16 kB │ map: 0.80 kB
|
|
219
|
-
dist/components/PlTextField/PlTextField.js 0.21 kB │ gzip: 0.16 kB │ map: 5.
|
|
219
|
+
dist/components/PlTextField/PlTextField.js 0.21 kB │ gzip: 0.16 kB │ map: 5.94 kB
|
|
220
220
|
dist/components/PlSlideModal/PlSlideModal.vue_vue_type_style_index_0_lang.module.js 0.21 kB │ gzip: 0.18 kB │ map: 0.41 kB
|
|
221
221
|
dist/components/DropdownListItem.js 0.21 kB │ gzip: 0.16 kB │ map: 2.20 kB
|
|
222
222
|
dist/components/PlBtnPrimary/PlBtnPrimary.js 0.21 kB │ gzip: 0.16 kB │ map: 1.12 kB
|
|
@@ -226,7 +226,7 @@ dist/components/DataTable/TableComponent.js
|
|
|
226
226
|
dist/components/PlCheckbox/PlCheckboxBase.js 0.22 kB │ gzip: 0.17 kB │ map: 0.73 kB
|
|
227
227
|
dist/components/PlDialogModal/PlDialogModal.js 0.22 kB │ gzip: 0.16 kB │ map: 4.61 kB
|
|
228
228
|
dist/components/PlDropdownRef/PlDropdownRef.js 0.22 kB │ gzip: 0.16 kB │ map: 3.27 kB
|
|
229
|
-
dist/components/PlNumberField/PlNumberField.js 0.22 kB │ gzip: 0.16 kB │ map: 10.
|
|
229
|
+
dist/components/PlNumberField/PlNumberField.js 0.22 kB │ gzip: 0.16 kB │ map: 10.25 kB
|
|
230
230
|
dist/components/PlProgressBar/PlProgressBar.js 0.22 kB │ gzip: 0.16 kB │ map: 0.93 kB
|
|
231
231
|
dist/components/DataTable/RowsCommandMenu.js 0.22 kB │ gzip: 0.17 kB │ map: 1.05 kB
|
|
232
232
|
dist/utils/DropdownOverlay/DropdownOverlay.js 0.22 kB │ gzip: 0.16 kB │ map: 2.40 kB
|
|
@@ -534,6 +534,14 @@ dist/composition/useQuery.js
|
|
|
534
534
|
dist/assets/icons/icon-assets-min/16_settings.js 0.64 kB │ gzip: 0.37 kB │ map: 0.71 kB
|
|
535
535
|
dist/assets/icons/icon-assets-min/24_table-upload.js 0.64 kB │ gzip: 0.38 kB │ map: 0.73 kB
|
|
536
536
|
dist/assets/icons/icon-assets-min/16_data-dimentions.js 0.64 kB │ gzip: 0.42 kB │ map: 0.74 kB
|
|
537
|
+
[33m[33m[PLUGIN_TIMINGS] Warning:[0m Your build spent significant time in plugins. Here is a breakdown:
|
|
538
|
+
- vite:asset (36%)
|
|
539
|
+
- vite:css-post (14%)
|
|
540
|
+
- sourcemaps (13%)
|
|
541
|
+
- vite:dts (10%)
|
|
542
|
+
- vite:vue (7%)
|
|
543
|
+
See https://rolldown.rs/options/checks#plugintimings for more details.
|
|
544
|
+
[39m
|
|
537
545
|
dist/assets/icons/icon-assets-min/16_clipboard.js 0.64 kB │ gzip: 0.38 kB │ map: 0.72 kB
|
|
538
546
|
dist/components/PlFileDialog/utils.js 0.64 kB │ gzip: 0.43 kB │ map: 2.18 kB
|
|
539
547
|
dist/assets/icons/icon-assets-min/24_frame-type-none.js 0.65 kB │ gzip: 0.30 kB │ map: 0.72 kB
|
|
@@ -644,14 +652,6 @@ dist/helpers/downloadContent.js
|
|
|
644
652
|
dist/assets/icons/icon-assets-min/24_boxplot-binned.js 1.00 kB │ gzip: 0.47 kB │ map: 1.09 kB
|
|
645
653
|
dist/assets/icons/icon-assets-min/24_bar-error.js 1.00 kB │ gzip: 0.43 kB │ map: 1.14 kB
|
|
646
654
|
dist/assets/icons/icon-assets-min/24_dendrogram-Y.js 1.00 kB │ gzip: 0.41 kB │ map: 1.14 kB
|
|
647
|
-
[33m[33m[PLUGIN_TIMINGS] Warning:[0m Your build spent significant time in plugins. Here is a breakdown:
|
|
648
|
-
- vite:asset (40%)
|
|
649
|
-
- vite:css-post (11%)
|
|
650
|
-
- vite:vue (10%)
|
|
651
|
-
- vite:dts (10%)
|
|
652
|
-
- sourcemaps (9%)
|
|
653
|
-
See https://rolldown.rs/options/checks#plugintimings for more details.
|
|
654
|
-
[39m
|
|
655
655
|
dist/assets/icons/icon-assets-min/24_view-show.js 1.01 kB │ gzip: 0.59 kB │ map: 1.09 kB
|
|
656
656
|
dist/components/DataTable/assets/TableIcon.js 1.01 kB │ gzip: 0.57 kB │ map: 1.02 kB
|
|
657
657
|
dist/assets/icons/icon-assets-min/24_theme-dark.js 1.02 kB │ gzip: 0.52 kB │ map: 1.09 kB
|
|
@@ -775,13 +775,13 @@ dist/components/PlLogView/PlLogView.vue_vue_type_script_setup_true_lang.js
|
|
|
775
775
|
dist/composition/usePollingQuery.js 3.83 kB │ gzip: 1.55 kB │ map: 17.23 kB
|
|
776
776
|
dist/composition/useSortable2.js 3.93 kB │ gzip: 1.41 kB │ map: 11.53 kB
|
|
777
777
|
dist/components/PlElementList/PlElementListItem.vue_vue_type_script_setup_true_lang.js 4.19 kB │ gzip: 1.35 kB │ map: 10.08 kB
|
|
778
|
-
dist/components/PlTextField/PlTextField.vue_vue_type_script_setup_true_lang.js 4.
|
|
778
|
+
dist/components/PlTextField/PlTextField.vue_vue_type_script_setup_true_lang.js 4.23 kB │ gzip: 1.71 kB │ map: 12.12 kB
|
|
779
779
|
dist/assets/icons/icon-assets-min/24_heatmap.js 4.50 kB │ gzip: 1.03 kB │ map: 4.65 kB
|
|
780
780
|
dist/components/PlBtnSplit/PlBtnSplit.vue_vue_type_script_setup_true_lang.js 4.61 kB │ gzip: 1.99 kB │ map: 13.49 kB
|
|
781
781
|
dist/components/Slider.vue_vue_type_script_setup_true_lang.js 4.67 kB │ gzip: 1.88 kB │ map: 8.92 kB
|
|
782
782
|
dist/components/PlFileInput/PlFileInput.vue_vue_type_script_setup_true_lang.js 5.27 kB │ gzip: 2.07 kB │ map: 14.80 kB
|
|
783
783
|
dist/components/SliderRange.vue_vue_type_script_setup_true_lang.js 5.92 kB │ gzip: 2.13 kB │ map: 12.27 kB
|
|
784
|
-
dist/components/PlNumberField/PlNumberField.vue_vue_type_script_setup_true_lang.js 6.
|
|
784
|
+
dist/components/PlNumberField/PlNumberField.vue_vue_type_script_setup_true_lang.js 6.28 kB │ gzip: 2.30 kB │ map: 17.76 kB
|
|
785
785
|
dist/components/PlDropdownLine/PlDropdownLine.vue_vue_type_script_setup_true_lang.js 7.23 kB │ gzip: 2.55 kB │ map: 21.04 kB
|
|
786
786
|
dist/components/PlFileDialog/Remote.vue_vue_type_script_setup_true_lang.js 7.32 kB │ gzip: 2.76 kB │ map: 15.24 kB
|
|
787
787
|
dist/components/SliderRangeTriple.vue_vue_type_script_setup_true_lang.js 7.35 kB │ gzip: 2.43 kB │ map: 15.58 kB
|
|
@@ -796,5 +796,5 @@ dist/composition/filters/metadata.js
|
|
|
796
796
|
dist/index.js 12.53 kB │ gzip: 2.96 kB │ map: 21.32 kB
|
|
797
797
|
dist/components/PlSvg/PlSvg.vue_vue_type_script_setup_true_lang.js 39.41 kB │ gzip: 3.40 kB │ map: 8.66 kB
|
|
798
798
|
|
|
799
|
-
[32m✓ built in 8.
|
|
799
|
+
[32m✓ built in 8.22s[39m
|
|
800
800
|
Build completed successfully
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
WARN Issue while reading "/home/runner/_work/platforma/platforma/.npmrc". Failed to replace env in config: ${NPMJS_TOKEN}
|
|
2
2
|
|
|
3
|
-
> @milaboratories/uikit@2.15.
|
|
3
|
+
> @milaboratories/uikit@2.15.21 formatter:check /home/runner/_work/platforma/platforma/lib/ui/uikit
|
|
4
4
|
> ts-builder formatter --check
|
|
5
5
|
|
|
6
6
|
Checking formatting...
|
|
@@ -8,5 +8,5 @@ Checking formatting...
|
|
|
8
8
|
Checking formatting...
|
|
9
9
|
|
|
10
10
|
All matched files use the correct format.
|
|
11
|
-
Finished in
|
|
11
|
+
Finished in 3033ms on 383 files using 16 threads.
|
|
12
12
|
Format check completed successfully
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
WARN Issue while reading "/home/runner/_work/platforma/platforma/.npmrc". Failed to replace env in config: ${NPMJS_TOKEN}
|
|
2
2
|
|
|
3
|
-
> @milaboratories/uikit@2.15.
|
|
3
|
+
> @milaboratories/uikit@2.15.21 linter:check /home/runner/_work/platforma/platforma/lib/ui/uikit
|
|
4
4
|
> ts-builder linter --check
|
|
5
5
|
|
|
6
6
|
Linting project...
|
|
7
7
|
↳ oxlint --config /home/runner/_work/platforma/platforma/lib/ui/uikit/.oxlintrc.json --deny-warnings
|
|
8
8
|
Found 0 warnings and 0 errors.
|
|
9
|
-
Finished in
|
|
9
|
+
Finished in 22ms on 310 files with 108 rules using 16 threads.
|
|
10
10
|
Linting completed successfully
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
WARN Issue while reading "/home/runner/_work/platforma/platforma/.npmrc". Failed to replace env in config: ${NPMJS_TOKEN}
|
|
2
2
|
|
|
3
|
-
> @milaboratories/uikit@2.15.
|
|
3
|
+
> @milaboratories/uikit@2.15.21 types:check /home/runner/_work/platforma/platforma/lib/ui/uikit
|
|
4
4
|
> ts-builder type-check --target browser-lib
|
|
5
5
|
|
|
6
6
|
↳ vue-tsc.js --noEmit --project ./tsconfig.json --customConditions ,
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @milaboratories/uikit
|
|
2
2
|
|
|
3
|
+
## 2.15.21
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- @platforma-sdk/model@1.80.17
|
|
8
|
+
- @milaboratories/helpers@1.14.5
|
|
9
|
+
|
|
10
|
+
## 2.15.20
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- 462418f: Fix `required` and `clearable` ignoring valueless attributes on PlTextField and PlNumberField
|
|
15
|
+
|
|
16
|
+
`<PlTextField required />` had no effect: neither the required marker nor the "Value is required"
|
|
17
|
+
check fired. A valueless attribute compiles to the empty string, and Vue coerces that to `true`
|
|
18
|
+
only when the prop's declared runtime type includes `Boolean` — but the SFC compiler cannot resolve
|
|
19
|
+
a bare type parameter, so `required?: R` emitted a typeless `required: {}` and the prop stayed
|
|
20
|
+
`""`. `clearable` hit the same rule from the other side: its conditional type erased the boolean
|
|
21
|
+
arm, leaving `{ type: Function }`, so a valueless `clearable` warned and did nothing.
|
|
22
|
+
|
|
23
|
+
Both props now intersect with `boolean`, which the compiler can resolve, restoring
|
|
24
|
+
`{ type: Boolean }` and `{ type: [Boolean, Function] }`. The type-level guard that forbids
|
|
25
|
+
`clearable` alongside `required` is unchanged.
|
|
26
|
+
|
|
3
27
|
## 2.15.19
|
|
4
28
|
|
|
5
29
|
### Patch Changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"valueless-attribute.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/valueless-attribute.test.ts"],"names":[],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlNumberField.js","names":[],"sources":["../../../src/components/PlNumberField/PlNumberField.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Number input field with increment/decrement buttons, validation, and min/max constraints.\n *\n * @example\n * <PlNumberField v-model=\"price\" :step=\"0.01\" :min-value=\"0\" label=\"Price\" />\n *\n * @example\n * <PlNumberField\n * v-model=\"evenNumber\"\n * :validate=\"(v) => v % 2 !== 0 ? 'Number must be even' : undefined\"\n * label=\"Even Number\"\n * />\n */\nexport default {\n name: \"PlNumberField\",\n};\n</script>\n\n<script\n setup\n lang=\"ts\"\n generic=\"\n R extends true | false,\n V extends undefined | number,\n C extends Exclude<V, [R] extends [true] ? undefined : never>\n \"\n>\nimport \"./pl-number-field.scss\";\nimport DoubleContour from \"../../utils/DoubleContour.vue\";\nimport { useLabelNotch } from \"../../utils/useLabelNotch\";\nimport { computed, ref, useSlots, watch } from \"vue\";\nimport { PlTooltip } from \"../PlTooltip\";\nimport { PlIcon16 } from \"../PlIcon16\";\nimport { tryParseNumber, numberToDecimalString, validateNumber } from \"./parseNumber\";\n\nconst modelValue = defineModel<V>({ required: true });\n\nconst emit = defineEmits<{\n blur: [value: V];\n focus: [value: V];\n enter: [value: V];\n}>();\n\nconst props = withDefaults(\n defineProps<{\n /** If `true`, the field is required and will show an error if left empty. */\n required?: R;\n /** Label on the top border of the field, empty by default */\n label?: string;\n /** Input placeholder, empty by default */\n placeholder?: string;\n /** Step for increment/decrement buttons, 1 by default */\n step?: number;\n /** If defined - show an error if value is lower */\n minValue?: number;\n /** If defined - show an error if value is higher */\n maxValue?: number;\n /** Input is disabled if true */\n disabled?: boolean;\n /** If true - remove buttons on the right */\n disableSteps?: boolean;\n /** Error message that shows always when it's provided, without other checks */\n errorMessage?: string;\n /** Additional validity check for input value that must return an error text if failed */\n validate?: (v: number) => string | undefined;\n /** If `true`, shows a clear button that resets value to `undefined`. If a function, calls it to get the reset value. */\n clearable?: (R extends true ? never : boolean) | (() => C);\n /** Makes some of corners not rounded */\n groupPosition?:\n | \"top\"\n | \"bottom\"\n | \"left\"\n | \"right\"\n | \"top-left\"\n | \"top-right\"\n | \"bottom-left\"\n | \"bottom-right\"\n | \"middle\";\n }>(),\n { step: 1 },\n);\n\nconst slots = useSlots();\n\nconst rootRef = ref<HTMLElement>();\n\nuseLabelNotch(rootRef);\n\nconst displayText = ref(numberToDecimalString(modelValue.value));\n\n// Sync display when model changes externally (parent, increment/decrement).\n// Skip if the current input already represents the same value.\nwatch(modelValue, (newVal) => {\n const parsed = tryParseNumber(displayText.value);\n if (parsed.value === newVal) return;\n displayText.value = numberToDecimalString(newVal);\n});\n\nfunction handleInput(event: Event) {\n const input = event.target as HTMLInputElement;\n displayText.value = input.value;\n\n const result = tryParseNumber(input.value);\n if (result.value !== undefined) {\n modelValue.value = result.value as V;\n }\n}\n\n// On Enter or blur: if parseable, replace display with canonical decimal string.\n// Converts exponential (1e-5) to plain form (0.00001).\nfunction commitValue() {\n const text = displayText.value.trim();\n const result = tryParseNumber(text);\n\n // Empty or partial (-, ., -.) → clear display and reset model for non-required fields\n if (text === \"\" || (result.value === undefined && result.error === undefined)) {\n displayText.value = \"\";\n if (!props.required) {\n modelValue.value = undefined as V;\n }\n return;\n }\n\n if (result.value !== undefined) {\n modelValue.value = result.value as V;\n displayText.value = numberToDecimalString(result.value);\n }\n}\n\nconst error = computed(() => {\n if (props.errorMessage) return props.errorMessage;\n\n const result = tryParseNumber(displayText.value);\n if (result.error) return result.error;\n if (result.value !== undefined) {\n return validateNumber(result.value, props);\n }\n\n if (props.required && displayText.value.trim() === \"\") {\n return \"Value is required\";\n }\n\n return undefined;\n});\n\nconst canShowClearable = computed(\n () =>\n props.clearable &&\n (modelValue.value !== undefined || displayText.value.trim() !== \"\") &&\n !props.disabled,\n);\n\nfunction clear() {\n if (typeof props.clearable === \"function\") {\n modelValue.value = props.clearable();\n displayText.value = numberToDecimalString(modelValue.value);\n } else {\n modelValue.value = undefined as V;\n displayText.value = \"\";\n }\n}\n\nconst isIncrementDisabled = computed(() => {\n if (error.value) return true;\n return (\n props.maxValue !== undefined &&\n modelValue.value !== undefined &&\n modelValue.value >= props.maxValue\n );\n});\n\nconst isDecrementDisabled = computed(() => {\n if (error.value) return true;\n return (\n props.minValue !== undefined &&\n modelValue.value !== undefined &&\n modelValue.value <= props.minValue\n );\n});\n\nconst multiplier = computed(() => 10 ** (props.step.toString().split(\".\").at(1)?.length ?? 0));\n\nfunction increment() {\n if (isIncrementDisabled.value) return;\n\n let nV: number;\n if (modelValue.value === undefined) {\n nV = props.minValue ?? 0;\n } else {\n nV =\n ((modelValue.value || 0) * multiplier.value + props.step * multiplier.value) /\n multiplier.value;\n }\n\n modelValue.value = (props.maxValue !== undefined ? Math.min(props.maxValue, nV) : nV) as V;\n}\n\nfunction decrement() {\n if (isDecrementDisabled.value) return;\n\n let nV: number;\n if (modelValue.value === undefined) {\n nV = 0;\n } else {\n nV =\n ((modelValue.value || 0) * multiplier.value - props.step * multiplier.value) /\n multiplier.value;\n }\n\n modelValue.value = (props.minValue !== undefined ? Math.max(props.minValue, nV) : nV) as V;\n}\n\nfunction handleBlur() {\n commitValue();\n emit(\"blur\", modelValue.value);\n}\n\nfunction handleFocus() {\n emit(\"focus\", modelValue.value);\n}\n\nfunction handleKeyDown(e: KeyboardEvent) {\n if (e.code === \"Enter\") {\n commitValue();\n emit(\"enter\", modelValue.value);\n }\n\n if ([\"ArrowDown\", \"ArrowUp\"].includes(e.code)) {\n e.preventDefault();\n }\n\n if (!props.disableSteps && e.code === \"ArrowUp\") {\n increment();\n }\n\n if (!props.disableSteps && e.code === \"ArrowDown\") {\n decrement();\n }\n}\n\n// Prevent selecting beyond input content on triple-click etc.\nfunction handleMousedown(ev: MouseEvent) {\n if (ev.detail > 1) {\n ev.preventDefault();\n }\n}\n</script>\n\n<template>\n <div\n ref=\"rootRef\"\n :class=\"{ error: !!error, disabled: disabled }\"\n class=\"pl-number-field d-flex-column\"\n @keydown=\"handleKeyDown\"\n >\n <div class=\"pl-number-field__main-wrapper d-flex\">\n <DoubleContour class=\"pl-number-field__contour\" :group-position=\"groupPosition\" />\n <div class=\"pl-number-field__wrapper flex-grow d-flex flex-align-center\">\n <label v-if=\"label\" class=\"text-description\">\n {{ label }}\n <PlTooltip v-if=\"slots.tooltip\" class=\"info\" position=\"top\">\n <template #tooltip>\n <slot name=\"tooltip\" />\n </template>\n </PlTooltip>\n </label>\n <input\n ref=\"inputRef\"\n type=\"text\"\n inputmode=\"numeric\"\n :value=\"displayText\"\n :disabled=\"disabled\"\n :placeholder=\"placeholder\"\n class=\"text-s flex-grow\"\n @input=\"handleInput\"\n @focusout=\"handleBlur\"\n @focusin=\"handleFocus\"\n />\n <PlIcon16\n v-if=\"canShowClearable\"\n class=\"pl-number-field__clearable\"\n name=\"delete-clear\"\n @click.stop=\"clear\"\n />\n </div>\n <div\n v-if=\"!props.disableSteps\"\n class=\"pl-number-field__icons d-flex-column\"\n @mousedown=\"handleMousedown\"\n >\n <div\n :class=\"{ disabled: isIncrementDisabled }\"\n class=\"pl-number-field__icon d-flex flex-justify-center uc-pointer flex-grow flex-align-center\"\n @click=\"increment\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M8 4.93933L13.5303 10.4697L12.4697 11.5303L8 7.06065L3.53033 11.5303L2.46967 10.4697L8 4.93933Z\"\n fill=\"#110529\"\n />\n </svg>\n </div>\n <div\n :class=\"{ disabled: isDecrementDisabled }\"\n class=\"pl-number-field__icon d-flex flex-justify-center uc-pointer flex-grow flex-align-center\"\n @click=\"decrement\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M2.46967 6.53033L3.53033 5.46967L8 9.93934L12.4697 5.46967L13.5303 6.53033L8 12.0607L2.46967 6.53033Z\"\n fill=\"#110529\"\n />\n </svg>\n </div>\n </div>\n </div>\n <div v-if=\"error\" class=\"pl-number-field__error\">\n {{ error }}\n </div>\n </div>\n</template>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"PlNumberField.js","names":[],"sources":["../../../src/components/PlNumberField/PlNumberField.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Number input field with increment/decrement buttons, validation, and min/max constraints.\n *\n * @example\n * <PlNumberField v-model=\"price\" :step=\"0.01\" :min-value=\"0\" label=\"Price\" />\n *\n * @example\n * <PlNumberField\n * v-model=\"evenNumber\"\n * :validate=\"(v) => v % 2 !== 0 ? 'Number must be even' : undefined\"\n * label=\"Even Number\"\n * />\n */\nexport default {\n name: \"PlNumberField\",\n};\n</script>\n\n<script\n setup\n lang=\"ts\"\n generic=\"\n R extends true | false,\n V extends undefined | number,\n C extends Exclude<V, [R] extends [true] ? undefined : never>\n \"\n>\nimport \"./pl-number-field.scss\";\nimport DoubleContour from \"../../utils/DoubleContour.vue\";\nimport type { BooleanProp, BooleanPropUnless } from \"../../utils/booleanProps\";\nimport { useLabelNotch } from \"../../utils/useLabelNotch\";\nimport { computed, ref, useSlots, watch } from \"vue\";\nimport { PlTooltip } from \"../PlTooltip\";\nimport { PlIcon16 } from \"../PlIcon16\";\nimport { tryParseNumber, numberToDecimalString, validateNumber } from \"./parseNumber\";\n\nconst modelValue = defineModel<V>({ required: true });\n\nconst emit = defineEmits<{\n blur: [value: V];\n focus: [value: V];\n enter: [value: V];\n}>();\n\nconst props = withDefaults(\n defineProps<{\n /** If `true`, the field is required and will show an error if left empty. */\n required?: BooleanProp<R>;\n /** Label on the top border of the field, empty by default */\n label?: string;\n /** Input placeholder, empty by default */\n placeholder?: string;\n /** Step for increment/decrement buttons, 1 by default */\n step?: number;\n /** If defined - show an error if value is lower */\n minValue?: number;\n /** If defined - show an error if value is higher */\n maxValue?: number;\n /** Input is disabled if true */\n disabled?: boolean;\n /** If true - remove buttons on the right */\n disableSteps?: boolean;\n /** Error message that shows always when it's provided, without other checks */\n errorMessage?: string;\n /** Additional validity check for input value that must return an error text if failed */\n validate?: (v: number) => string | undefined;\n /** If `true`, shows a clear button that resets value to `undefined`. If a function, calls it to get the reset value. */\n clearable?: BooleanPropUnless<R> | (() => C);\n /** Makes some of corners not rounded */\n groupPosition?:\n | \"top\"\n | \"bottom\"\n | \"left\"\n | \"right\"\n | \"top-left\"\n | \"top-right\"\n | \"bottom-left\"\n | \"bottom-right\"\n | \"middle\";\n }>(),\n { step: 1 },\n);\n\nconst slots = useSlots();\n\nconst rootRef = ref<HTMLElement>();\n\nuseLabelNotch(rootRef);\n\nconst displayText = ref(numberToDecimalString(modelValue.value));\n\n// Sync display when model changes externally (parent, increment/decrement).\n// Skip if the current input already represents the same value.\nwatch(modelValue, (newVal) => {\n const parsed = tryParseNumber(displayText.value);\n if (parsed.value === newVal) return;\n displayText.value = numberToDecimalString(newVal);\n});\n\nfunction handleInput(event: Event) {\n const input = event.target as HTMLInputElement;\n displayText.value = input.value;\n\n const result = tryParseNumber(input.value);\n if (result.value !== undefined) {\n modelValue.value = result.value as V;\n }\n}\n\n// On Enter or blur: if parseable, replace display with canonical decimal string.\n// Converts exponential (1e-5) to plain form (0.00001).\nfunction commitValue() {\n const text = displayText.value.trim();\n const result = tryParseNumber(text);\n\n // Empty or partial (-, ., -.) → clear display and reset model for non-required fields\n if (text === \"\" || (result.value === undefined && result.error === undefined)) {\n displayText.value = \"\";\n if (!props.required) {\n modelValue.value = undefined as V;\n }\n return;\n }\n\n if (result.value !== undefined) {\n modelValue.value = result.value as V;\n displayText.value = numberToDecimalString(result.value);\n }\n}\n\nconst error = computed(() => {\n if (props.errorMessage) return props.errorMessage;\n\n const result = tryParseNumber(displayText.value);\n if (result.error) return result.error;\n if (result.value !== undefined) {\n return validateNumber(result.value, props);\n }\n\n if (props.required && displayText.value.trim() === \"\") {\n return \"Value is required\";\n }\n\n return undefined;\n});\n\nconst canShowClearable = computed(\n () =>\n props.clearable &&\n (modelValue.value !== undefined || displayText.value.trim() !== \"\") &&\n !props.disabled,\n);\n\nfunction clear() {\n if (typeof props.clearable === \"function\") {\n modelValue.value = props.clearable();\n displayText.value = numberToDecimalString(modelValue.value);\n } else {\n modelValue.value = undefined as V;\n displayText.value = \"\";\n }\n}\n\nconst isIncrementDisabled = computed(() => {\n if (error.value) return true;\n return (\n props.maxValue !== undefined &&\n modelValue.value !== undefined &&\n modelValue.value >= props.maxValue\n );\n});\n\nconst isDecrementDisabled = computed(() => {\n if (error.value) return true;\n return (\n props.minValue !== undefined &&\n modelValue.value !== undefined &&\n modelValue.value <= props.minValue\n );\n});\n\nconst multiplier = computed(() => 10 ** (props.step.toString().split(\".\").at(1)?.length ?? 0));\n\nfunction increment() {\n if (isIncrementDisabled.value) return;\n\n let nV: number;\n if (modelValue.value === undefined) {\n nV = props.minValue ?? 0;\n } else {\n nV =\n ((modelValue.value || 0) * multiplier.value + props.step * multiplier.value) /\n multiplier.value;\n }\n\n modelValue.value = (props.maxValue !== undefined ? Math.min(props.maxValue, nV) : nV) as V;\n}\n\nfunction decrement() {\n if (isDecrementDisabled.value) return;\n\n let nV: number;\n if (modelValue.value === undefined) {\n nV = 0;\n } else {\n nV =\n ((modelValue.value || 0) * multiplier.value - props.step * multiplier.value) /\n multiplier.value;\n }\n\n modelValue.value = (props.minValue !== undefined ? Math.max(props.minValue, nV) : nV) as V;\n}\n\nfunction handleBlur() {\n commitValue();\n emit(\"blur\", modelValue.value);\n}\n\nfunction handleFocus() {\n emit(\"focus\", modelValue.value);\n}\n\nfunction handleKeyDown(e: KeyboardEvent) {\n if (e.code === \"Enter\") {\n commitValue();\n emit(\"enter\", modelValue.value);\n }\n\n if ([\"ArrowDown\", \"ArrowUp\"].includes(e.code)) {\n e.preventDefault();\n }\n\n if (!props.disableSteps && e.code === \"ArrowUp\") {\n increment();\n }\n\n if (!props.disableSteps && e.code === \"ArrowDown\") {\n decrement();\n }\n}\n\n// Prevent selecting beyond input content on triple-click etc.\nfunction handleMousedown(ev: MouseEvent) {\n if (ev.detail > 1) {\n ev.preventDefault();\n }\n}\n</script>\n\n<template>\n <div\n ref=\"rootRef\"\n :class=\"{ error: !!error, disabled: disabled }\"\n class=\"pl-number-field d-flex-column\"\n @keydown=\"handleKeyDown\"\n >\n <div class=\"pl-number-field__main-wrapper d-flex\">\n <DoubleContour class=\"pl-number-field__contour\" :group-position=\"groupPosition\" />\n <div class=\"pl-number-field__wrapper flex-grow d-flex flex-align-center\">\n <label v-if=\"label\" class=\"text-description\">\n {{ label }}\n <PlTooltip v-if=\"slots.tooltip\" class=\"info\" position=\"top\">\n <template #tooltip>\n <slot name=\"tooltip\" />\n </template>\n </PlTooltip>\n </label>\n <input\n ref=\"inputRef\"\n type=\"text\"\n inputmode=\"numeric\"\n :value=\"displayText\"\n :disabled=\"disabled\"\n :placeholder=\"placeholder\"\n class=\"text-s flex-grow\"\n @input=\"handleInput\"\n @focusout=\"handleBlur\"\n @focusin=\"handleFocus\"\n />\n <PlIcon16\n v-if=\"canShowClearable\"\n class=\"pl-number-field__clearable\"\n name=\"delete-clear\"\n @click.stop=\"clear\"\n />\n </div>\n <div\n v-if=\"!props.disableSteps\"\n class=\"pl-number-field__icons d-flex-column\"\n @mousedown=\"handleMousedown\"\n >\n <div\n :class=\"{ disabled: isIncrementDisabled }\"\n class=\"pl-number-field__icon d-flex flex-justify-center uc-pointer flex-grow flex-align-center\"\n @click=\"increment\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M8 4.93933L13.5303 10.4697L12.4697 11.5303L8 7.06065L3.53033 11.5303L2.46967 10.4697L8 4.93933Z\"\n fill=\"#110529\"\n />\n </svg>\n </div>\n <div\n :class=\"{ disabled: isDecrementDisabled }\"\n class=\"pl-number-field__icon d-flex flex-justify-center uc-pointer flex-grow flex-align-center\"\n @click=\"decrement\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M2.46967 6.53033L3.53033 5.46967L8 9.93934L12.4697 5.46967L13.5303 6.53033L8 12.0607L2.46967 6.53033Z\"\n fill=\"#110529\"\n />\n </svg>\n </div>\n </div>\n </div>\n <div v-if=\"error\" class=\"pl-number-field__error\">\n {{ error }}\n </div>\n </div>\n</template>\n"],"mappings":""}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BooleanProp, BooleanPropUnless } from '../../utils/booleanProps';
|
|
1
2
|
/**
|
|
2
3
|
* Number input field with increment/decrement buttons, validation, and min/max constraints.
|
|
3
4
|
*
|
|
@@ -21,7 +22,7 @@ declare const _default: <R extends true | false, V extends undefined | number, C
|
|
|
21
22
|
modelValue: V;
|
|
22
23
|
} & {
|
|
23
24
|
/** If `true`, the field is required and will show an error if left empty. */
|
|
24
|
-
required?: R
|
|
25
|
+
required?: BooleanProp<R>;
|
|
25
26
|
/** Label on the top border of the field, empty by default */
|
|
26
27
|
label?: string;
|
|
27
28
|
/** Input placeholder, empty by default */
|
|
@@ -41,7 +42,7 @@ declare const _default: <R extends true | false, V extends undefined | number, C
|
|
|
41
42
|
/** Additional validity check for input value that must return an error text if failed */
|
|
42
43
|
validate?: (v: number) => string | undefined;
|
|
43
44
|
/** If `true`, shows a clear button that resets value to `undefined`. If a function, calls it to get the reset value. */
|
|
44
|
-
clearable?:
|
|
45
|
+
clearable?: BooleanPropUnless<R> | (() => C);
|
|
45
46
|
/** Makes some of corners not rounded */
|
|
46
47
|
groupPosition?: "top" | "bottom" | "left" | "right" | "top-left" | "top-right" | "bottom-left" | "bottom-right" | "middle";
|
|
47
48
|
}) & Partial<{}>> & import('vue').PublicProps;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlNumberField.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlNumberField/PlNumberField.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PlNumberField.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlNumberField/PlNumberField.vue"],"names":[],"mappings":"AAqVA,OAAO,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAQ/E;;;;;;;;;;;;GAYG;yBAEC,CAAC,SAAS,IAAI,GAAG,KAAK,EACtB,CAAC,SAAS,SAAS,GAAG,MAAM,EAC5B,CAAC,SAAS,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,SAAS,GAAG,KAAK,CAAC,EAE/D,aAAa,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,EAC9D,YAAY,mBAAmB,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,EAC3G,eAAe,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,EACjE;WAwaO,mBAAmB,CAAC;;;;;;oBAvMhB,CAAC;;QAtNT,6EAA6E;mBAClE,WAAW,CAAC,CAAC,CAAC;QACzB,6DAA6D;gBACrD,MAAM;QACd,0CAA0C;sBAC5B,MAAM;QACpB,yDAAyD;eAClD,MAAM;QACb,oDAAoD;mBACzC,MAAM;QACjB,oDAAoD;mBACzC,MAAM;QACjB,gCAAgC;mBACrB,OAAO;QAClB,4CAA4C;uBAC7B,OAAO;QACtB,+EAA+E;uBAChE,MAAM;QACrB,yFAAyF;mBAC9E,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS;QAC5C,wHAAwH;oBAC5G,iBAAiB,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC5C,wCAAwC;wBAEpC,KAAK,GACL,QAAQ,GACR,MAAM,GACN,OAAO,GACP,UAAU,GACV,WAAW,GACX,aAAa,GACb,cAAc,GACd,QAAQ;oBA6X8E,CAAC,4BAA2B;oBACzG,OAAO,KAAK,EAAE,gBAAgB,CAAC,EAAE,CAAC,GAAG,IAAI;WAClD,GAAG;;yBAnDmB,GAAG;;UAqD1B,8JAAoC;EAEvC,KACQ,OAAO,KAAK,EAAE,KAAK,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC,OAAO,WAAW,CAAC,CAAA;CAAE;AAvbzE,wBAub4E;AAG5E,KAAK,mBAAmB,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,GAAG,EAAE,CAAC"}
|
|
@@ -21,7 +21,7 @@ var D = { class: "pl-number-field__main-wrapper d-flex" }, O = { class: "pl-numb
|
|
|
21
21
|
}, M = /* @__PURE__ */ m({
|
|
22
22
|
name: "PlNumberField",
|
|
23
23
|
props: /* @__PURE__ */ h({
|
|
24
|
-
required: {},
|
|
24
|
+
required: { type: Boolean },
|
|
25
25
|
label: {},
|
|
26
26
|
placeholder: {},
|
|
27
27
|
step: { default: 1 },
|
|
@@ -31,7 +31,7 @@ var D = { class: "pl-number-field__main-wrapper d-flex" }, O = { class: "pl-numb
|
|
|
31
31
|
disableSteps: { type: Boolean },
|
|
32
32
|
errorMessage: {},
|
|
33
33
|
validate: {},
|
|
34
|
-
clearable: {},
|
|
34
|
+
clearable: { type: [Boolean, Function] },
|
|
35
35
|
groupPosition: {}
|
|
36
36
|
}, {
|
|
37
37
|
modelValue: { required: !0 },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlNumberField.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/PlNumberField/PlNumberField.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Number input field with increment/decrement buttons, validation, and min/max constraints.\n *\n * @example\n * <PlNumberField v-model=\"price\" :step=\"0.01\" :min-value=\"0\" label=\"Price\" />\n *\n * @example\n * <PlNumberField\n * v-model=\"evenNumber\"\n * :validate=\"(v) => v % 2 !== 0 ? 'Number must be even' : undefined\"\n * label=\"Even Number\"\n * />\n */\nexport default {\n name: \"PlNumberField\",\n};\n</script>\n\n<script\n setup\n lang=\"ts\"\n generic=\"\n R extends true | false,\n V extends undefined | number,\n C extends Exclude<V, [R] extends [true] ? undefined : never>\n \"\n>\nimport \"./pl-number-field.scss\";\nimport DoubleContour from \"../../utils/DoubleContour.vue\";\nimport { useLabelNotch } from \"../../utils/useLabelNotch\";\nimport { computed, ref, useSlots, watch } from \"vue\";\nimport { PlTooltip } from \"../PlTooltip\";\nimport { PlIcon16 } from \"../PlIcon16\";\nimport { tryParseNumber, numberToDecimalString, validateNumber } from \"./parseNumber\";\n\nconst modelValue = defineModel<V>({ required: true });\n\nconst emit = defineEmits<{\n blur: [value: V];\n focus: [value: V];\n enter: [value: V];\n}>();\n\nconst props = withDefaults(\n defineProps<{\n /** If `true`, the field is required and will show an error if left empty. */\n required?: R;\n /** Label on the top border of the field, empty by default */\n label?: string;\n /** Input placeholder, empty by default */\n placeholder?: string;\n /** Step for increment/decrement buttons, 1 by default */\n step?: number;\n /** If defined - show an error if value is lower */\n minValue?: number;\n /** If defined - show an error if value is higher */\n maxValue?: number;\n /** Input is disabled if true */\n disabled?: boolean;\n /** If true - remove buttons on the right */\n disableSteps?: boolean;\n /** Error message that shows always when it's provided, without other checks */\n errorMessage?: string;\n /** Additional validity check for input value that must return an error text if failed */\n validate?: (v: number) => string | undefined;\n /** If `true`, shows a clear button that resets value to `undefined`. If a function, calls it to get the reset value. */\n clearable?: (R extends true ? never : boolean) | (() => C);\n /** Makes some of corners not rounded */\n groupPosition?:\n | \"top\"\n | \"bottom\"\n | \"left\"\n | \"right\"\n | \"top-left\"\n | \"top-right\"\n | \"bottom-left\"\n | \"bottom-right\"\n | \"middle\";\n }>(),\n { step: 1 },\n);\n\nconst slots = useSlots();\n\nconst rootRef = ref<HTMLElement>();\n\nuseLabelNotch(rootRef);\n\nconst displayText = ref(numberToDecimalString(modelValue.value));\n\n// Sync display when model changes externally (parent, increment/decrement).\n// Skip if the current input already represents the same value.\nwatch(modelValue, (newVal) => {\n const parsed = tryParseNumber(displayText.value);\n if (parsed.value === newVal) return;\n displayText.value = numberToDecimalString(newVal);\n});\n\nfunction handleInput(event: Event) {\n const input = event.target as HTMLInputElement;\n displayText.value = input.value;\n\n const result = tryParseNumber(input.value);\n if (result.value !== undefined) {\n modelValue.value = result.value as V;\n }\n}\n\n// On Enter or blur: if parseable, replace display with canonical decimal string.\n// Converts exponential (1e-5) to plain form (0.00001).\nfunction commitValue() {\n const text = displayText.value.trim();\n const result = tryParseNumber(text);\n\n // Empty or partial (-, ., -.) → clear display and reset model for non-required fields\n if (text === \"\" || (result.value === undefined && result.error === undefined)) {\n displayText.value = \"\";\n if (!props.required) {\n modelValue.value = undefined as V;\n }\n return;\n }\n\n if (result.value !== undefined) {\n modelValue.value = result.value as V;\n displayText.value = numberToDecimalString(result.value);\n }\n}\n\nconst error = computed(() => {\n if (props.errorMessage) return props.errorMessage;\n\n const result = tryParseNumber(displayText.value);\n if (result.error) return result.error;\n if (result.value !== undefined) {\n return validateNumber(result.value, props);\n }\n\n if (props.required && displayText.value.trim() === \"\") {\n return \"Value is required\";\n }\n\n return undefined;\n});\n\nconst canShowClearable = computed(\n () =>\n props.clearable &&\n (modelValue.value !== undefined || displayText.value.trim() !== \"\") &&\n !props.disabled,\n);\n\nfunction clear() {\n if (typeof props.clearable === \"function\") {\n modelValue.value = props.clearable();\n displayText.value = numberToDecimalString(modelValue.value);\n } else {\n modelValue.value = undefined as V;\n displayText.value = \"\";\n }\n}\n\nconst isIncrementDisabled = computed(() => {\n if (error.value) return true;\n return (\n props.maxValue !== undefined &&\n modelValue.value !== undefined &&\n modelValue.value >= props.maxValue\n );\n});\n\nconst isDecrementDisabled = computed(() => {\n if (error.value) return true;\n return (\n props.minValue !== undefined &&\n modelValue.value !== undefined &&\n modelValue.value <= props.minValue\n );\n});\n\nconst multiplier = computed(() => 10 ** (props.step.toString().split(\".\").at(1)?.length ?? 0));\n\nfunction increment() {\n if (isIncrementDisabled.value) return;\n\n let nV: number;\n if (modelValue.value === undefined) {\n nV = props.minValue ?? 0;\n } else {\n nV =\n ((modelValue.value || 0) * multiplier.value + props.step * multiplier.value) /\n multiplier.value;\n }\n\n modelValue.value = (props.maxValue !== undefined ? Math.min(props.maxValue, nV) : nV) as V;\n}\n\nfunction decrement() {\n if (isDecrementDisabled.value) return;\n\n let nV: number;\n if (modelValue.value === undefined) {\n nV = 0;\n } else {\n nV =\n ((modelValue.value || 0) * multiplier.value - props.step * multiplier.value) /\n multiplier.value;\n }\n\n modelValue.value = (props.minValue !== undefined ? Math.max(props.minValue, nV) : nV) as V;\n}\n\nfunction handleBlur() {\n commitValue();\n emit(\"blur\", modelValue.value);\n}\n\nfunction handleFocus() {\n emit(\"focus\", modelValue.value);\n}\n\nfunction handleKeyDown(e: KeyboardEvent) {\n if (e.code === \"Enter\") {\n commitValue();\n emit(\"enter\", modelValue.value);\n }\n\n if ([\"ArrowDown\", \"ArrowUp\"].includes(e.code)) {\n e.preventDefault();\n }\n\n if (!props.disableSteps && e.code === \"ArrowUp\") {\n increment();\n }\n\n if (!props.disableSteps && e.code === \"ArrowDown\") {\n decrement();\n }\n}\n\n// Prevent selecting beyond input content on triple-click etc.\nfunction handleMousedown(ev: MouseEvent) {\n if (ev.detail > 1) {\n ev.preventDefault();\n }\n}\n</script>\n\n<template>\n <div\n ref=\"rootRef\"\n :class=\"{ error: !!error, disabled: disabled }\"\n class=\"pl-number-field d-flex-column\"\n @keydown=\"handleKeyDown\"\n >\n <div class=\"pl-number-field__main-wrapper d-flex\">\n <DoubleContour class=\"pl-number-field__contour\" :group-position=\"groupPosition\" />\n <div class=\"pl-number-field__wrapper flex-grow d-flex flex-align-center\">\n <label v-if=\"label\" class=\"text-description\">\n {{ label }}\n <PlTooltip v-if=\"slots.tooltip\" class=\"info\" position=\"top\">\n <template #tooltip>\n <slot name=\"tooltip\" />\n </template>\n </PlTooltip>\n </label>\n <input\n ref=\"inputRef\"\n type=\"text\"\n inputmode=\"numeric\"\n :value=\"displayText\"\n :disabled=\"disabled\"\n :placeholder=\"placeholder\"\n class=\"text-s flex-grow\"\n @input=\"handleInput\"\n @focusout=\"handleBlur\"\n @focusin=\"handleFocus\"\n />\n <PlIcon16\n v-if=\"canShowClearable\"\n class=\"pl-number-field__clearable\"\n name=\"delete-clear\"\n @click.stop=\"clear\"\n />\n </div>\n <div\n v-if=\"!props.disableSteps\"\n class=\"pl-number-field__icons d-flex-column\"\n @mousedown=\"handleMousedown\"\n >\n <div\n :class=\"{ disabled: isIncrementDisabled }\"\n class=\"pl-number-field__icon d-flex flex-justify-center uc-pointer flex-grow flex-align-center\"\n @click=\"increment\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M8 4.93933L13.5303 10.4697L12.4697 11.5303L8 7.06065L3.53033 11.5303L2.46967 10.4697L8 4.93933Z\"\n fill=\"#110529\"\n />\n </svg>\n </div>\n <div\n :class=\"{ disabled: isDecrementDisabled }\"\n class=\"pl-number-field__icon d-flex flex-justify-center uc-pointer flex-grow flex-align-center\"\n @click=\"decrement\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M2.46967 6.53033L3.53033 5.46967L8 9.93934L12.4697 5.46967L13.5303 6.53033L8 12.0607L2.46967 6.53033Z\"\n fill=\"#110529\"\n />\n </svg>\n </div>\n </div>\n </div>\n <div v-if=\"error\" class=\"pl-number-field__error\">\n {{ error }}\n </div>\n </div>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PlNumberField.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/PlNumberField/PlNumberField.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Number input field with increment/decrement buttons, validation, and min/max constraints.\n *\n * @example\n * <PlNumberField v-model=\"price\" :step=\"0.01\" :min-value=\"0\" label=\"Price\" />\n *\n * @example\n * <PlNumberField\n * v-model=\"evenNumber\"\n * :validate=\"(v) => v % 2 !== 0 ? 'Number must be even' : undefined\"\n * label=\"Even Number\"\n * />\n */\nexport default {\n name: \"PlNumberField\",\n};\n</script>\n\n<script\n setup\n lang=\"ts\"\n generic=\"\n R extends true | false,\n V extends undefined | number,\n C extends Exclude<V, [R] extends [true] ? undefined : never>\n \"\n>\nimport \"./pl-number-field.scss\";\nimport DoubleContour from \"../../utils/DoubleContour.vue\";\nimport type { BooleanProp, BooleanPropUnless } from \"../../utils/booleanProps\";\nimport { useLabelNotch } from \"../../utils/useLabelNotch\";\nimport { computed, ref, useSlots, watch } from \"vue\";\nimport { PlTooltip } from \"../PlTooltip\";\nimport { PlIcon16 } from \"../PlIcon16\";\nimport { tryParseNumber, numberToDecimalString, validateNumber } from \"./parseNumber\";\n\nconst modelValue = defineModel<V>({ required: true });\n\nconst emit = defineEmits<{\n blur: [value: V];\n focus: [value: V];\n enter: [value: V];\n}>();\n\nconst props = withDefaults(\n defineProps<{\n /** If `true`, the field is required and will show an error if left empty. */\n required?: BooleanProp<R>;\n /** Label on the top border of the field, empty by default */\n label?: string;\n /** Input placeholder, empty by default */\n placeholder?: string;\n /** Step for increment/decrement buttons, 1 by default */\n step?: number;\n /** If defined - show an error if value is lower */\n minValue?: number;\n /** If defined - show an error if value is higher */\n maxValue?: number;\n /** Input is disabled if true */\n disabled?: boolean;\n /** If true - remove buttons on the right */\n disableSteps?: boolean;\n /** Error message that shows always when it's provided, without other checks */\n errorMessage?: string;\n /** Additional validity check for input value that must return an error text if failed */\n validate?: (v: number) => string | undefined;\n /** If `true`, shows a clear button that resets value to `undefined`. If a function, calls it to get the reset value. */\n clearable?: BooleanPropUnless<R> | (() => C);\n /** Makes some of corners not rounded */\n groupPosition?:\n | \"top\"\n | \"bottom\"\n | \"left\"\n | \"right\"\n | \"top-left\"\n | \"top-right\"\n | \"bottom-left\"\n | \"bottom-right\"\n | \"middle\";\n }>(),\n { step: 1 },\n);\n\nconst slots = useSlots();\n\nconst rootRef = ref<HTMLElement>();\n\nuseLabelNotch(rootRef);\n\nconst displayText = ref(numberToDecimalString(modelValue.value));\n\n// Sync display when model changes externally (parent, increment/decrement).\n// Skip if the current input already represents the same value.\nwatch(modelValue, (newVal) => {\n const parsed = tryParseNumber(displayText.value);\n if (parsed.value === newVal) return;\n displayText.value = numberToDecimalString(newVal);\n});\n\nfunction handleInput(event: Event) {\n const input = event.target as HTMLInputElement;\n displayText.value = input.value;\n\n const result = tryParseNumber(input.value);\n if (result.value !== undefined) {\n modelValue.value = result.value as V;\n }\n}\n\n// On Enter or blur: if parseable, replace display with canonical decimal string.\n// Converts exponential (1e-5) to plain form (0.00001).\nfunction commitValue() {\n const text = displayText.value.trim();\n const result = tryParseNumber(text);\n\n // Empty or partial (-, ., -.) → clear display and reset model for non-required fields\n if (text === \"\" || (result.value === undefined && result.error === undefined)) {\n displayText.value = \"\";\n if (!props.required) {\n modelValue.value = undefined as V;\n }\n return;\n }\n\n if (result.value !== undefined) {\n modelValue.value = result.value as V;\n displayText.value = numberToDecimalString(result.value);\n }\n}\n\nconst error = computed(() => {\n if (props.errorMessage) return props.errorMessage;\n\n const result = tryParseNumber(displayText.value);\n if (result.error) return result.error;\n if (result.value !== undefined) {\n return validateNumber(result.value, props);\n }\n\n if (props.required && displayText.value.trim() === \"\") {\n return \"Value is required\";\n }\n\n return undefined;\n});\n\nconst canShowClearable = computed(\n () =>\n props.clearable &&\n (modelValue.value !== undefined || displayText.value.trim() !== \"\") &&\n !props.disabled,\n);\n\nfunction clear() {\n if (typeof props.clearable === \"function\") {\n modelValue.value = props.clearable();\n displayText.value = numberToDecimalString(modelValue.value);\n } else {\n modelValue.value = undefined as V;\n displayText.value = \"\";\n }\n}\n\nconst isIncrementDisabled = computed(() => {\n if (error.value) return true;\n return (\n props.maxValue !== undefined &&\n modelValue.value !== undefined &&\n modelValue.value >= props.maxValue\n );\n});\n\nconst isDecrementDisabled = computed(() => {\n if (error.value) return true;\n return (\n props.minValue !== undefined &&\n modelValue.value !== undefined &&\n modelValue.value <= props.minValue\n );\n});\n\nconst multiplier = computed(() => 10 ** (props.step.toString().split(\".\").at(1)?.length ?? 0));\n\nfunction increment() {\n if (isIncrementDisabled.value) return;\n\n let nV: number;\n if (modelValue.value === undefined) {\n nV = props.minValue ?? 0;\n } else {\n nV =\n ((modelValue.value || 0) * multiplier.value + props.step * multiplier.value) /\n multiplier.value;\n }\n\n modelValue.value = (props.maxValue !== undefined ? Math.min(props.maxValue, nV) : nV) as V;\n}\n\nfunction decrement() {\n if (isDecrementDisabled.value) return;\n\n let nV: number;\n if (modelValue.value === undefined) {\n nV = 0;\n } else {\n nV =\n ((modelValue.value || 0) * multiplier.value - props.step * multiplier.value) /\n multiplier.value;\n }\n\n modelValue.value = (props.minValue !== undefined ? Math.max(props.minValue, nV) : nV) as V;\n}\n\nfunction handleBlur() {\n commitValue();\n emit(\"blur\", modelValue.value);\n}\n\nfunction handleFocus() {\n emit(\"focus\", modelValue.value);\n}\n\nfunction handleKeyDown(e: KeyboardEvent) {\n if (e.code === \"Enter\") {\n commitValue();\n emit(\"enter\", modelValue.value);\n }\n\n if ([\"ArrowDown\", \"ArrowUp\"].includes(e.code)) {\n e.preventDefault();\n }\n\n if (!props.disableSteps && e.code === \"ArrowUp\") {\n increment();\n }\n\n if (!props.disableSteps && e.code === \"ArrowDown\") {\n decrement();\n }\n}\n\n// Prevent selecting beyond input content on triple-click etc.\nfunction handleMousedown(ev: MouseEvent) {\n if (ev.detail > 1) {\n ev.preventDefault();\n }\n}\n</script>\n\n<template>\n <div\n ref=\"rootRef\"\n :class=\"{ error: !!error, disabled: disabled }\"\n class=\"pl-number-field d-flex-column\"\n @keydown=\"handleKeyDown\"\n >\n <div class=\"pl-number-field__main-wrapper d-flex\">\n <DoubleContour class=\"pl-number-field__contour\" :group-position=\"groupPosition\" />\n <div class=\"pl-number-field__wrapper flex-grow d-flex flex-align-center\">\n <label v-if=\"label\" class=\"text-description\">\n {{ label }}\n <PlTooltip v-if=\"slots.tooltip\" class=\"info\" position=\"top\">\n <template #tooltip>\n <slot name=\"tooltip\" />\n </template>\n </PlTooltip>\n </label>\n <input\n ref=\"inputRef\"\n type=\"text\"\n inputmode=\"numeric\"\n :value=\"displayText\"\n :disabled=\"disabled\"\n :placeholder=\"placeholder\"\n class=\"text-s flex-grow\"\n @input=\"handleInput\"\n @focusout=\"handleBlur\"\n @focusin=\"handleFocus\"\n />\n <PlIcon16\n v-if=\"canShowClearable\"\n class=\"pl-number-field__clearable\"\n name=\"delete-clear\"\n @click.stop=\"clear\"\n />\n </div>\n <div\n v-if=\"!props.disableSteps\"\n class=\"pl-number-field__icons d-flex-column\"\n @mousedown=\"handleMousedown\"\n >\n <div\n :class=\"{ disabled: isIncrementDisabled }\"\n class=\"pl-number-field__icon d-flex flex-justify-center uc-pointer flex-grow flex-align-center\"\n @click=\"increment\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M8 4.93933L13.5303 10.4697L12.4697 11.5303L8 7.06065L3.53033 11.5303L2.46967 10.4697L8 4.93933Z\"\n fill=\"#110529\"\n />\n </svg>\n </div>\n <div\n :class=\"{ disabled: isDecrementDisabled }\"\n class=\"pl-number-field__icon d-flex flex-justify-center uc-pointer flex-grow flex-align-center\"\n @click=\"decrement\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M2.46967 6.53033L3.53033 5.46967L8 9.93934L12.4697 5.46967L13.5303 6.53033L8 12.0607L2.46967 6.53033Z\"\n fill=\"#110529\"\n />\n </svg>\n </div>\n </div>\n </div>\n <div v-if=\"error\" class=\"pl-number-field__error\">\n {{ error }}\n </div>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlTextField.js","names":[],"sources":["../../../src/components/PlTextField/PlTextField.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Component for one-line string data manipulation\n */\nexport default {\n name: \"PlTextField\",\n};\n</script>\n\n<script\n lang=\"ts\"\n setup\n generic=\"\n R extends true | false,\n V extends undefined | string,\n C extends Exclude<V, [R] extends [true] ? undefined : never>\n \"\n>\nimport { computed, ref, useSlots } from \"vue\";\nimport SvgRequired from \"../../assets/images/required.svg?raw\";\nimport { getErrorMessage } from \"../../helpers/error.ts\";\nimport DoubleContour from \"../../utils/DoubleContour.vue\";\nimport { useLabelNotch } from \"../../utils/useLabelNotch\";\nimport { PlIcon16 } from \"../PlIcon16\";\nimport { PlIcon24 } from \"../PlIcon24\";\nimport { PlSvg } from \"../PlSvg\";\nimport { PlTooltip } from \"../PlTooltip\";\nimport \"./pl-text-field.scss\";\n\nconst slots = useSlots();\n\n/**\n * The current value of the input field.\n */\nconst model = defineModel<V>({\n required: true,\n});\n\nconst props = defineProps<{\n /** The string specifies whether the field should be a password or not, value could be \"password\" or undefined. */\n type?: \"password\";\n /** The label to display above the input field. */\n label?: string;\n /** If `true`, the input field is marked as required and will show an error if left empty. */\n required?: R
|
|
1
|
+
{"version":3,"file":"PlTextField.js","names":[],"sources":["../../../src/components/PlTextField/PlTextField.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Component for one-line string data manipulation\n */\nexport default {\n name: \"PlTextField\",\n};\n</script>\n\n<script\n lang=\"ts\"\n setup\n generic=\"\n R extends true | false,\n V extends undefined | string,\n C extends Exclude<V, [R] extends [true] ? undefined : never>\n \"\n>\nimport { computed, ref, useSlots } from \"vue\";\nimport SvgRequired from \"../../assets/images/required.svg?raw\";\nimport { getErrorMessage } from \"../../helpers/error.ts\";\nimport DoubleContour from \"../../utils/DoubleContour.vue\";\nimport type { BooleanProp, BooleanPropUnless } from \"../../utils/booleanProps\";\nimport { useLabelNotch } from \"../../utils/useLabelNotch\";\nimport { PlIcon16 } from \"../PlIcon16\";\nimport { PlIcon24 } from \"../PlIcon24\";\nimport { PlSvg } from \"../PlSvg\";\nimport { PlTooltip } from \"../PlTooltip\";\nimport \"./pl-text-field.scss\";\n\nconst slots = useSlots();\n\n/**\n * The current value of the input field.\n */\nconst model = defineModel<V>({\n required: true,\n});\n\nconst props = defineProps<{\n /** The string specifies whether the field should be a password or not, value could be \"password\" or undefined. */\n type?: \"password\";\n /** The label to display above the input field. */\n label?: string;\n /** If `true`, the input field is marked as required and will show an error if left empty. */\n required?: BooleanProp<R>;\n /** An error message to display below the input field. */\n error?: unknown;\n /** A helper text to display below the input field when there are no errors. */\n helper?: string;\n /** A placeholder text to display inside the input field when it is empty. */\n placeholder?: string;\n /** If `true`, the input field is disabled and cannot be interacted with. */\n disabled?: boolean;\n /** If `true`, the input field has a dashed border. */\n dashed?: boolean;\n /** A prefix text to display inside the input field before the value. */\n prefix?: string;\n /**\n * If `true`, a clear icon will appear in the input field to clear the value (set it to empty string).\n * If a function, calls it to get the reset value.\n */\n clearable?: BooleanPropUnless<R> | (() => C);\n /** Additional validity check for input value that must return an error text if failed */\n validate?: (v: V) => string | undefined;\n /** Makes some of corners not rounded */\n groupPosition?:\n | \"top\"\n | \"bottom\"\n | \"left\"\n | \"right\"\n | \"top-left\"\n | \"top-right\"\n | \"bottom-left\"\n | \"bottom-right\"\n | \"middle\";\n}>();\n\nconst rootRef = ref<HTMLInputElement | undefined>(undefined);\n\nconst inputRef = ref<HTMLInputElement | undefined>();\n\nconst showPassword = ref(false);\n\nconst fieldType = computed(() => {\n if (props.type && props.type === \"password\") {\n return showPassword.value ? \"text\" : props.type;\n } else {\n return \"text\";\n }\n});\n\nconst passwordIcon = computed(() => (showPassword.value ? \"view-show\" : \"view-hide\"));\n\nconst clear = () => {\n if (props.clearable) {\n model.value = (typeof props.clearable === \"function\" ? props.clearable() : \"\") as V;\n }\n};\n\nconst isEmpty = computed(() => model.value === \"\");\n\nconst displayErrors = computed(() => {\n const errors: string[] = [];\n const propsError = getErrorMessage(props.error);\n if (propsError) {\n errors.push(propsError);\n }\n if (props.validate) {\n const error = props.validate(model.value as V);\n if (error) {\n errors.push(error);\n }\n }\n if (props.required && isEmpty.value) {\n errors.push(\"Value is required\");\n }\n return errors;\n});\n\nconst hasErrors = computed(() => displayErrors.value.length > 0);\n\nconst canShowClearable = computed(\n () => props.clearable && !isEmpty.value && props.type !== \"password\" && !props.disabled,\n);\n\nconst togglePasswordVisibility = () => (showPassword.value = !showPassword.value);\n\nconst setFocusOnInput = () => inputRef.value?.focus();\n\nuseLabelNotch(rootRef);\n</script>\n\n<template>\n <div class=\"pl-text-field__envelope\">\n <div\n ref=\"rootRef\"\n class=\"pl-text-field\"\n :class=\"{\n error: hasErrors,\n disabled,\n dashed,\n nonEmpty: !isEmpty,\n }\"\n >\n <label v-if=\"label\" ref=\"label\">\n <PlSvg v-if=\"required\" :uri=\"SvgRequired\" />\n <span>{{ label }}</span>\n <PlTooltip v-if=\"slots.tooltip\" class=\"info\" position=\"top\">\n <template #tooltip>\n <slot name=\"tooltip\" />\n </template>\n </PlTooltip>\n </label>\n <div v-if=\"prefix\" class=\"pl-text-field__prefix\">\n {{ prefix }}\n </div>\n <input\n ref=\"inputRef\"\n v-model=\"model\"\n :disabled=\"disabled\"\n :placeholder=\"placeholder || '...'\"\n :type=\"fieldType\"\n spellcheck=\"false\"\n />\n <div class=\"pl-text-field__append\" @click=\"setFocusOnInput\">\n <PlIcon16\n v-if=\"canShowClearable\"\n class=\"pl-text-field__clearable\"\n name=\"delete-clear\"\n @click.stop=\"clear\"\n />\n <PlIcon24\n v-if=\"type === 'password'\"\n :name=\"passwordIcon\"\n style=\"cursor: pointer\"\n @click.stop=\"togglePasswordVisibility\"\n />\n <slot name=\"append\" />\n </div>\n <DoubleContour class=\"pl-text-field__contour\" :group-position=\"groupPosition\" />\n </div>\n <div v-if=\"hasErrors\" class=\"pl-text-field__error\">\n {{ displayErrors.join(\" \") }}\n </div>\n <div v-else-if=\"helper\" class=\"pl-text-field__helper\">{{ helper }}</div>\n </div>\n</template>\n"],"mappings":""}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BooleanProp, BooleanPropUnless } from '../../utils/booleanProps';
|
|
1
2
|
/**
|
|
2
3
|
* Component for one-line string data manipulation
|
|
3
4
|
*/
|
|
@@ -12,7 +13,7 @@ declare const _default: <R extends true | false, V extends undefined | string, C
|
|
|
12
13
|
/** The label to display above the input field. */
|
|
13
14
|
label?: string;
|
|
14
15
|
/** If `true`, the input field is marked as required and will show an error if left empty. */
|
|
15
|
-
required?: R
|
|
16
|
+
required?: BooleanProp<R>;
|
|
16
17
|
/** An error message to display below the input field. */
|
|
17
18
|
error?: unknown;
|
|
18
19
|
/** A helper text to display below the input field when there are no errors. */
|
|
@@ -29,7 +30,7 @@ declare const _default: <R extends true | false, V extends undefined | string, C
|
|
|
29
30
|
* If `true`, a clear icon will appear in the input field to clear the value (set it to empty string).
|
|
30
31
|
* If a function, calls it to get the reset value.
|
|
31
32
|
*/
|
|
32
|
-
clearable?:
|
|
33
|
+
clearable?: BooleanPropUnless<R> | (() => C);
|
|
33
34
|
/** Additional validity check for input value that must return an error text if failed */
|
|
34
35
|
validate?: (v: V) => string | undefined;
|
|
35
36
|
/** Makes some of corners not rounded */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlTextField.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlTextField/PlTextField.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PlTextField.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlTextField/PlTextField.vue"],"names":[],"mappings":"AAkMA,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAM/E,OAAO,sBAAsB,CAAC;AAG9B;;GAEG;yBAEC,CAAC,SAAS,IAAI,GAAG,KAAK,EACtB,CAAC,SAAS,SAAS,GAAG,MAAM,EAC5B,CAAC,SAAS,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,SAAS,GAAG,KAAK,CAAC,EAE/D,aAAa,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,EAC9D,YAAY,mBAAmB,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,EAC3G,eAAe,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,EACjE;WAmUO,mBAAmB,CAAC;;;oBAlNhB,CAAC;;QAtGX,kHAAkH;eAC3G,UAAU;QACjB,kDAAkD;gBAC1C,MAAM;QACd,6FAA6F;mBAClF,WAAW,CAAC,CAAC,CAAC;QACzB,yDAAyD;gBACjD,OAAO;QACf,+EAA+E;iBACtE,MAAM;QACf,6EAA6E;sBAC/D,MAAM;QACpB,4EAA4E;mBACjE,OAAO;QAClB,sDAAsD;iBAC7C,OAAO;QAChB,wEAAwE;iBAC/D,MAAM;QACf;;;WAGG;oBACS,iBAAiB,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC5C,yFAAyF;mBAC9E,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,GAAG,SAAS;QACvC,wCAAwC;wBAEpC,KAAK,GACL,QAAQ,GACR,MAAM,GACN,OAAO,GACP,UAAU,GACV,WAAW,GACX,aAAa,GACb,cAAc,GACd,QAAQ;oBAqRgF,CAAC,4BAA2B;oBACzG,OAAO,KAAK,EAAE,gBAAgB,CAAC,EAAE,CAAC,GAAG,IAAI;WAClD,GAAG;;yBArDmB,GAAG;wBACJ,GAAG;;;EAwD5B,KACQ,OAAO,KAAK,EAAE,KAAK,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC,OAAO,WAAW,CAAC,CAAA;CAAE;AAlVzE,wBAkV4E;AAG5E,KAAK,mBAAmB,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,GAAG,EAAE,CAAC"}
|
|
@@ -34,14 +34,14 @@ var O = { class: "pl-text-field__envelope" }, k = {
|
|
|
34
34
|
props: /* @__PURE__ */ h({
|
|
35
35
|
type: {},
|
|
36
36
|
label: {},
|
|
37
|
-
required: {},
|
|
37
|
+
required: { type: Boolean },
|
|
38
38
|
error: {},
|
|
39
39
|
helper: {},
|
|
40
40
|
placeholder: {},
|
|
41
41
|
disabled: { type: Boolean },
|
|
42
42
|
dashed: { type: Boolean },
|
|
43
43
|
prefix: {},
|
|
44
|
-
clearable: { type: Function },
|
|
44
|
+
clearable: { type: [Boolean, Function] },
|
|
45
45
|
validate: { type: Function },
|
|
46
46
|
groupPosition: {}
|
|
47
47
|
}, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlTextField.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/PlTextField/PlTextField.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Component for one-line string data manipulation\n */\nexport default {\n name: \"PlTextField\",\n};\n</script>\n\n<script\n lang=\"ts\"\n setup\n generic=\"\n R extends true | false,\n V extends undefined | string,\n C extends Exclude<V, [R] extends [true] ? undefined : never>\n \"\n>\nimport { computed, ref, useSlots } from \"vue\";\nimport SvgRequired from \"../../assets/images/required.svg?raw\";\nimport { getErrorMessage } from \"../../helpers/error.ts\";\nimport DoubleContour from \"../../utils/DoubleContour.vue\";\nimport { useLabelNotch } from \"../../utils/useLabelNotch\";\nimport { PlIcon16 } from \"../PlIcon16\";\nimport { PlIcon24 } from \"../PlIcon24\";\nimport { PlSvg } from \"../PlSvg\";\nimport { PlTooltip } from \"../PlTooltip\";\nimport \"./pl-text-field.scss\";\n\nconst slots = useSlots();\n\n/**\n * The current value of the input field.\n */\nconst model = defineModel<V>({\n required: true,\n});\n\nconst props = defineProps<{\n /** The string specifies whether the field should be a password or not, value could be \"password\" or undefined. */\n type?: \"password\";\n /** The label to display above the input field. */\n label?: string;\n /** If `true`, the input field is marked as required and will show an error if left empty. */\n required?: R
|
|
1
|
+
{"version":3,"file":"PlTextField.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/PlTextField/PlTextField.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Component for one-line string data manipulation\n */\nexport default {\n name: \"PlTextField\",\n};\n</script>\n\n<script\n lang=\"ts\"\n setup\n generic=\"\n R extends true | false,\n V extends undefined | string,\n C extends Exclude<V, [R] extends [true] ? undefined : never>\n \"\n>\nimport { computed, ref, useSlots } from \"vue\";\nimport SvgRequired from \"../../assets/images/required.svg?raw\";\nimport { getErrorMessage } from \"../../helpers/error.ts\";\nimport DoubleContour from \"../../utils/DoubleContour.vue\";\nimport type { BooleanProp, BooleanPropUnless } from \"../../utils/booleanProps\";\nimport { useLabelNotch } from \"../../utils/useLabelNotch\";\nimport { PlIcon16 } from \"../PlIcon16\";\nimport { PlIcon24 } from \"../PlIcon24\";\nimport { PlSvg } from \"../PlSvg\";\nimport { PlTooltip } from \"../PlTooltip\";\nimport \"./pl-text-field.scss\";\n\nconst slots = useSlots();\n\n/**\n * The current value of the input field.\n */\nconst model = defineModel<V>({\n required: true,\n});\n\nconst props = defineProps<{\n /** The string specifies whether the field should be a password or not, value could be \"password\" or undefined. */\n type?: \"password\";\n /** The label to display above the input field. */\n label?: string;\n /** If `true`, the input field is marked as required and will show an error if left empty. */\n required?: BooleanProp<R>;\n /** An error message to display below the input field. */\n error?: unknown;\n /** A helper text to display below the input field when there are no errors. */\n helper?: string;\n /** A placeholder text to display inside the input field when it is empty. */\n placeholder?: string;\n /** If `true`, the input field is disabled and cannot be interacted with. */\n disabled?: boolean;\n /** If `true`, the input field has a dashed border. */\n dashed?: boolean;\n /** A prefix text to display inside the input field before the value. */\n prefix?: string;\n /**\n * If `true`, a clear icon will appear in the input field to clear the value (set it to empty string).\n * If a function, calls it to get the reset value.\n */\n clearable?: BooleanPropUnless<R> | (() => C);\n /** Additional validity check for input value that must return an error text if failed */\n validate?: (v: V) => string | undefined;\n /** Makes some of corners not rounded */\n groupPosition?:\n | \"top\"\n | \"bottom\"\n | \"left\"\n | \"right\"\n | \"top-left\"\n | \"top-right\"\n | \"bottom-left\"\n | \"bottom-right\"\n | \"middle\";\n}>();\n\nconst rootRef = ref<HTMLInputElement | undefined>(undefined);\n\nconst inputRef = ref<HTMLInputElement | undefined>();\n\nconst showPassword = ref(false);\n\nconst fieldType = computed(() => {\n if (props.type && props.type === \"password\") {\n return showPassword.value ? \"text\" : props.type;\n } else {\n return \"text\";\n }\n});\n\nconst passwordIcon = computed(() => (showPassword.value ? \"view-show\" : \"view-hide\"));\n\nconst clear = () => {\n if (props.clearable) {\n model.value = (typeof props.clearable === \"function\" ? props.clearable() : \"\") as V;\n }\n};\n\nconst isEmpty = computed(() => model.value === \"\");\n\nconst displayErrors = computed(() => {\n const errors: string[] = [];\n const propsError = getErrorMessage(props.error);\n if (propsError) {\n errors.push(propsError);\n }\n if (props.validate) {\n const error = props.validate(model.value as V);\n if (error) {\n errors.push(error);\n }\n }\n if (props.required && isEmpty.value) {\n errors.push(\"Value is required\");\n }\n return errors;\n});\n\nconst hasErrors = computed(() => displayErrors.value.length > 0);\n\nconst canShowClearable = computed(\n () => props.clearable && !isEmpty.value && props.type !== \"password\" && !props.disabled,\n);\n\nconst togglePasswordVisibility = () => (showPassword.value = !showPassword.value);\n\nconst setFocusOnInput = () => inputRef.value?.focus();\n\nuseLabelNotch(rootRef);\n</script>\n\n<template>\n <div class=\"pl-text-field__envelope\">\n <div\n ref=\"rootRef\"\n class=\"pl-text-field\"\n :class=\"{\n error: hasErrors,\n disabled,\n dashed,\n nonEmpty: !isEmpty,\n }\"\n >\n <label v-if=\"label\" ref=\"label\">\n <PlSvg v-if=\"required\" :uri=\"SvgRequired\" />\n <span>{{ label }}</span>\n <PlTooltip v-if=\"slots.tooltip\" class=\"info\" position=\"top\">\n <template #tooltip>\n <slot name=\"tooltip\" />\n </template>\n </PlTooltip>\n </label>\n <div v-if=\"prefix\" class=\"pl-text-field__prefix\">\n {{ prefix }}\n </div>\n <input\n ref=\"inputRef\"\n v-model=\"model\"\n :disabled=\"disabled\"\n :placeholder=\"placeholder || '...'\"\n :type=\"fieldType\"\n spellcheck=\"false\"\n />\n <div class=\"pl-text-field__append\" @click=\"setFocusOnInput\">\n <PlIcon16\n v-if=\"canShowClearable\"\n class=\"pl-text-field__clearable\"\n name=\"delete-clear\"\n @click.stop=\"clear\"\n />\n <PlIcon24\n v-if=\"type === 'password'\"\n :name=\"passwordIcon\"\n style=\"cursor: pointer\"\n @click.stop=\"togglePasswordVisibility\"\n />\n <slot name=\"append\" />\n </div>\n <DoubleContour class=\"pl-text-field__contour\" :group-position=\"groupPosition\" />\n </div>\n <div v-if=\"hasErrors\" class=\"pl-text-field__error\">\n {{ displayErrors.join(\" \") }}\n </div>\n <div v-else-if=\"helper\" class=\"pl-text-field__helper\">{{ helper }}</div>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAKE,MAAM;;;;;;;;;;;;;;;;;;;;EAyBR,IAAM,IAAQ,GAAU,EAKlB,IAAQ,EAAc,GAAA,aAE1B,EAEI,IAAQ,GAuCR,IAAU,EAAkC,KAAA,EAAU,EAEtD,IAAW,GAAmC,EAE9C,IAAe,EAAI,GAAM,EAEzB,IAAY,QACZ,EAAM,QAAQ,EAAM,SAAS,aACxB,EAAa,QAAQ,SAAS,EAAM,OAEpC,OAET,EAEI,IAAe,QAAgB,EAAa,QAAQ,cAAc,YAAa,EAE/E,UAAc;AAClB,GAAI,EAAM,cACR,EAAM,QAAS,OAAO,EAAM,aAAc,aAAa,EAAM,WAAW,GAAG;KAIzE,IAAU,QAAe,EAAM,UAAU,GAAG,EAE5C,IAAgB,QAAe;GACnC,IAAM,IAAmB,EAAE,EACrB,IAAa,EAAgB,EAAM,MAAM;AAI/C,OAHI,KACF,EAAO,KAAK,EAAW,EAErB,EAAM,UAAU;IAClB,IAAM,IAAQ,EAAM,SAAS,EAAM,MAAW;AAC9C,IAAI,KACF,EAAO,KAAK,EAAM;;AAMtB,UAHI,EAAM,YAAY,EAAQ,SAC5B,EAAO,KAAK,oBAAoB,EAE3B;IACP,EAEI,IAAY,QAAe,EAAc,MAAM,SAAS,EAAE,EAE1D,IAAmB,QACjB,EAAM,aAAa,CAAC,EAAQ,SAAS,EAAM,SAAS,cAAc,CAAC,EAAM,SAChF,EAEK,UAAkC,EAAa,QAAQ,CAAC,EAAa,OAErE,UAAwB,EAAS,OAAO,OAAO;SAErD,EAAc,EAAQ,kBAIpB,EAoDM,OApDN,GAoDM,CAnDJ,EA8CM,OAAA;YA7CA;GAAJ,KAAI;GACJ,OAAK,EAAA,CAAC,iBAAe;WACI,EAAA;cAAmB,EAAA;YAAkB,EAAA;eAA2B,EAAA;;;GAO5E,EAAA,SAAA,GAAA,EAAb,EAQQ,SARR,GAQQ;IAPO,EAAA,YAAA,GAAA,EAAb,EAA4C,EAAA,EAAA,EAAA;;KAApB,KAAK,EAAA,EAAW;;IACxC,EAAwB,QAAA,MAAA,EAAf,EAAA,MAAK,EAAA,EAAA;IACG,EAAA,EAAK,CAAC,WAAA,GAAA,EAAvB,EAIY,EAAA,EAAA,EAAA;;KAJoB,OAAM;KAAO,UAAS;;KACzC,SAAO,QACO,CAAvB,EAAuB,EAAA,QAAA,UAAA,CAAA,CAAA;;;;GAIlB,EAAA,UAAA,GAAA,EAAX,EAEM,OAFN,GAEM,EADD,EAAA,OAAM,EAAA,EAAA,IAAA,EAAA,IAAA,GAAA;KAEX,EAOE,SAAA;aANI;IAAJ,KAAI;6CACU,QAAA;IACb,UAAU,EAAA;IACV,aAAa,EAAA,eAAW;IACxB,MAAM,EAAA;IACP,YAAW;wBAJF,EAAA,MAAK,CAAA,CAAA;GAMhB,EAcM,OAAA;IAdD,OAAM;IAAyB,SAAO;;IAEjC,EAAA,SAAA,GAAA,EADR,EAKE,EAAA,EAAA,EAAA;;KAHA,OAAM;KACN,MAAK;KACJ,SAAK,EAAO,GAAK,CAAA,OAAA,CAAA;;IAGZ,EAAA,SAAI,cAAA,GAAA,EADZ,EAKE,EAAA,EAAA,EAAA;;KAHC,MAAM,EAAA;KACP,OAAA,EAAA,QAAA,WAAuB;KACtB,SAAK,EAAO,GAAwB,CAAA,OAAA,CAAA;;IAEvC,EAAsB,EAAA,QAAA,SAAA;;GAExB,EAAgF,GAAA;IAAjE,OAAM;IAA0B,kBAAgB,EAAA;;SAEtD,EAAA,SAAA,GAAA,EAAX,EAEM,OAFN,GAEM,EADD,EAAA,MAAc,KAAI,IAAA,CAAA,EAAA,EAAA,IAEP,EAAA,UAAA,GAAA,EAAhB,EAAwE,OAAxE,GAAwE,EAAf,EAAA,OAAM,EAAA,EAAA,IAAA,EAAA,IAAA,GAAA,CAAA,CAAA"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boolean prop types for components that are generic over one of their boolean props.
|
|
3
|
+
*
|
|
4
|
+
* A valueless attribute (`<PlTextField required />`) passes the empty string, and Vue coerces that
|
|
5
|
+
* to `true` only for props whose *runtime* type includes `Boolean`. The SFC compiler derives those
|
|
6
|
+
* runtime types from the source, and it cannot resolve either a bare type parameter or a
|
|
7
|
+
* conditional type — it emits a prop with no type at all, which silently ignores the attribute.
|
|
8
|
+
*
|
|
9
|
+
* Both aliases below keep `boolean` at the top level of the type so the compiler still emits
|
|
10
|
+
* `Boolean`, without giving up the type parameter that correlates the props.
|
|
11
|
+
*/
|
|
12
|
+
/** A `boolean` prop whose literal value is still inferred into `Flag`. */
|
|
13
|
+
export type BooleanProp<Flag extends boolean> = Flag & boolean;
|
|
14
|
+
/** A `boolean` prop that is not accepted at all when `Condition` is `true`. */
|
|
15
|
+
export type BooleanPropUnless<Condition extends boolean> = boolean & ([Condition] extends [true] ? never : unknown);
|
|
16
|
+
//# sourceMappingURL=booleanProps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"booleanProps.d.ts","sourceRoot":"","sources":["../../src/utils/booleanProps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,0EAA0E;AAC1E,MAAM,MAAM,WAAW,CAAC,IAAI,SAAS,OAAO,IAAI,IAAI,GAAG,OAAO,CAAC;AAE/D,+EAA+E;AAC/E,MAAM,MAAM,iBAAiB,CAAC,SAAS,SAAS,OAAO,IAAI,OAAO,GAChE,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,KAAK,GAAG,OAAO,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@milaboratories/uikit",
|
|
3
|
-
"version": "2.15.
|
|
3
|
+
"version": "2.15.21",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"sortablejs": "^1.15.6",
|
|
33
33
|
"vue": "3.5.24",
|
|
34
34
|
"@milaboratories/helpers": "1.14.5",
|
|
35
|
-
"@platforma-sdk/model": "1.80.
|
|
35
|
+
"@platforma-sdk/model": "1.80.17"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@vitest/coverage-istanbul": "^4.1.3",
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
"svgo": "^3.3.2",
|
|
41
41
|
"typescript": "~5.9.3",
|
|
42
42
|
"vitest": "^4.1.3",
|
|
43
|
-
"@milaboratories/
|
|
44
|
-
"@milaboratories/ts-configs": "1.
|
|
45
|
-
"@milaboratories/
|
|
43
|
+
"@milaboratories/ts-builder": "1.6.2",
|
|
44
|
+
"@milaboratories/ts-configs": "1.4.0",
|
|
45
|
+
"@milaboratories/build-configs": "2.0.0"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"dev": "ts-builder serve --target browser-lib --build-config ./build.browser-lib.config.js",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { mount } from "@vue/test-utils";
|
|
4
|
+
import PlTextField from "../components/PlTextField/PlTextField.vue";
|
|
5
|
+
import PlNumberField from "../components/PlNumberField/PlNumberField.vue";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A valueless attribute (`<PlTextField required />`) passes the empty string, and Vue coerces that
|
|
9
|
+
* to `true` only for props whose runtime type includes Boolean. A prop declared as a bare type
|
|
10
|
+
* parameter or as a conditional type compiles to one with no runtime type, which silently keeps
|
|
11
|
+
* the `""` and reads as false everywhere it is used.
|
|
12
|
+
*
|
|
13
|
+
* Passed through `attrs`, because that is what a valueless attribute in a template produces, and
|
|
14
|
+
* because the empty string is deliberately not a valid value for either prop.
|
|
15
|
+
*/
|
|
16
|
+
describe.each([
|
|
17
|
+
["PlTextField", PlTextField, ""],
|
|
18
|
+
["PlNumberField", PlNumberField, undefined],
|
|
19
|
+
])("%s coerces valueless attributes", (_name, Component, emptyValue) => {
|
|
20
|
+
it("treats a valueless `required` as true", () => {
|
|
21
|
+
const w = mount(Component, {
|
|
22
|
+
props: { modelValue: emptyValue, label: "L" },
|
|
23
|
+
attrs: { required: "" },
|
|
24
|
+
});
|
|
25
|
+
expect(w.props("required")).toBe(true);
|
|
26
|
+
expect(w.text()).toContain("Value is required");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("treats a valueless `clearable` as true", () => {
|
|
30
|
+
const w = mount(Component, {
|
|
31
|
+
props: { modelValue: emptyValue, label: "L" },
|
|
32
|
+
attrs: { clearable: "" },
|
|
33
|
+
});
|
|
34
|
+
expect(w.props("clearable")).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("leaves both falsy when the attribute is absent", () => {
|
|
38
|
+
const w = mount(Component, { props: { modelValue: emptyValue, label: "L" } });
|
|
39
|
+
expect(w.props("required")).toBeFalsy();
|
|
40
|
+
expect(w.props("clearable")).toBeFalsy();
|
|
41
|
+
expect(w.text()).not.toContain("Value is required");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -28,6 +28,7 @@ export default {
|
|
|
28
28
|
>
|
|
29
29
|
import "./pl-number-field.scss";
|
|
30
30
|
import DoubleContour from "../../utils/DoubleContour.vue";
|
|
31
|
+
import type { BooleanProp, BooleanPropUnless } from "../../utils/booleanProps";
|
|
31
32
|
import { useLabelNotch } from "../../utils/useLabelNotch";
|
|
32
33
|
import { computed, ref, useSlots, watch } from "vue";
|
|
33
34
|
import { PlTooltip } from "../PlTooltip";
|
|
@@ -45,7 +46,7 @@ const emit = defineEmits<{
|
|
|
45
46
|
const props = withDefaults(
|
|
46
47
|
defineProps<{
|
|
47
48
|
/** If `true`, the field is required and will show an error if left empty. */
|
|
48
|
-
required?: R
|
|
49
|
+
required?: BooleanProp<R>;
|
|
49
50
|
/** Label on the top border of the field, empty by default */
|
|
50
51
|
label?: string;
|
|
51
52
|
/** Input placeholder, empty by default */
|
|
@@ -65,7 +66,7 @@ const props = withDefaults(
|
|
|
65
66
|
/** Additional validity check for input value that must return an error text if failed */
|
|
66
67
|
validate?: (v: number) => string | undefined;
|
|
67
68
|
/** If `true`, shows a clear button that resets value to `undefined`. If a function, calls it to get the reset value. */
|
|
68
|
-
clearable?:
|
|
69
|
+
clearable?: BooleanPropUnless<R> | (() => C);
|
|
69
70
|
/** Makes some of corners not rounded */
|
|
70
71
|
groupPosition?:
|
|
71
72
|
| "top"
|
|
@@ -20,6 +20,7 @@ import { computed, ref, useSlots } from "vue";
|
|
|
20
20
|
import SvgRequired from "../../assets/images/required.svg?raw";
|
|
21
21
|
import { getErrorMessage } from "../../helpers/error.ts";
|
|
22
22
|
import DoubleContour from "../../utils/DoubleContour.vue";
|
|
23
|
+
import type { BooleanProp, BooleanPropUnless } from "../../utils/booleanProps";
|
|
23
24
|
import { useLabelNotch } from "../../utils/useLabelNotch";
|
|
24
25
|
import { PlIcon16 } from "../PlIcon16";
|
|
25
26
|
import { PlIcon24 } from "../PlIcon24";
|
|
@@ -42,7 +43,7 @@ const props = defineProps<{
|
|
|
42
43
|
/** The label to display above the input field. */
|
|
43
44
|
label?: string;
|
|
44
45
|
/** If `true`, the input field is marked as required and will show an error if left empty. */
|
|
45
|
-
required?: R
|
|
46
|
+
required?: BooleanProp<R>;
|
|
46
47
|
/** An error message to display below the input field. */
|
|
47
48
|
error?: unknown;
|
|
48
49
|
/** A helper text to display below the input field when there are no errors. */
|
|
@@ -59,7 +60,7 @@ const props = defineProps<{
|
|
|
59
60
|
* If `true`, a clear icon will appear in the input field to clear the value (set it to empty string).
|
|
60
61
|
* If a function, calls it to get the reset value.
|
|
61
62
|
*/
|
|
62
|
-
clearable?:
|
|
63
|
+
clearable?: BooleanPropUnless<R> | (() => C);
|
|
63
64
|
/** Additional validity check for input value that must return an error text if failed */
|
|
64
65
|
validate?: (v: V) => string | undefined;
|
|
65
66
|
/** Makes some of corners not rounded */
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boolean prop types for components that are generic over one of their boolean props.
|
|
3
|
+
*
|
|
4
|
+
* A valueless attribute (`<PlTextField required />`) passes the empty string, and Vue coerces that
|
|
5
|
+
* to `true` only for props whose *runtime* type includes `Boolean`. The SFC compiler derives those
|
|
6
|
+
* runtime types from the source, and it cannot resolve either a bare type parameter or a
|
|
7
|
+
* conditional type — it emits a prop with no type at all, which silently ignores the attribute.
|
|
8
|
+
*
|
|
9
|
+
* Both aliases below keep `boolean` at the top level of the type so the compiler still emits
|
|
10
|
+
* `Boolean`, without giving up the type parameter that correlates the props.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** A `boolean` prop whose literal value is still inferred into `Flag`. */
|
|
14
|
+
export type BooleanProp<Flag extends boolean> = Flag & boolean;
|
|
15
|
+
|
|
16
|
+
/** A `boolean` prop that is not accepted at all when `Condition` is `true`. */
|
|
17
|
+
export type BooleanPropUnless<Condition extends boolean> = boolean &
|
|
18
|
+
([Condition] extends [true] ? never : unknown);
|