@tachui/forms 0.8.15 → 0.8.17
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/{Select-yZyKooXk.js → Select-C2CbXEop.js} +247 -242
- package/dist/{Slider-0-oal5YR.js → Slider-6ybJi_Iw.js} +2 -2
- package/dist/{TextField-hX15dY3U.js → TextField-qobWm59g.js} +166 -159
- package/dist/components/advanced/index.js +1 -1
- package/dist/components/selection/Checkbox.d.ts.map +1 -1
- package/dist/components/selection/Select.d.ts.map +1 -1
- package/dist/components/selection/index.js +1 -1
- package/dist/components/text-input/TextField.d.ts.map +1 -1
- package/dist/components/text-input/index.js +1 -1
- package/dist/index.js +3 -3
- package/dist/modifiers/index.js +157 -110
- package/dist/modifiers/placeholder.d.ts.map +1 -1
- package/dist/modifiers/required.d.ts +5 -3
- package/dist/modifiers/required.d.ts.map +1 -1
- package/dist/modifiers/validation.d.ts +2 -1
- package/dist/modifiers/validation.d.ts.map +1 -1
- package/dist/types/index.d.ts +4 -2
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/components/advanced/Slider.ts +2 -2
- package/src/components/selection/Checkbox.ts +14 -5
- package/src/components/selection/Select.ts +14 -4
- package/src/components/text-input/TextField.ts +31 -2
- package/src/modifiers/placeholder.ts +13 -1
- package/src/modifiers/required.ts +62 -8
- package/src/modifiers/validation.ts +48 -13
- package/src/types/index.ts +4 -1
|
@@ -47,6 +47,7 @@ export const TextField: Component<TextFieldProps> = props => {
|
|
|
47
47
|
autocomplete,
|
|
48
48
|
spellcheck = true,
|
|
49
49
|
disabled = false,
|
|
50
|
+
readOnly = false,
|
|
50
51
|
required = false,
|
|
51
52
|
validation,
|
|
52
53
|
value: controlledValue,
|
|
@@ -74,6 +75,7 @@ export const TextField: Component<TextFieldProps> = props => {
|
|
|
74
75
|
text: textSignal,
|
|
75
76
|
placeholderSignal,
|
|
76
77
|
disabledSignal,
|
|
78
|
+
readOnlySignal,
|
|
77
79
|
|
|
78
80
|
...restProps
|
|
79
81
|
} = props
|
|
@@ -96,6 +98,17 @@ export const TextField: Component<TextFieldProps> = props => {
|
|
|
96
98
|
const [currentText, setCurrentText] = createSignal('')
|
|
97
99
|
const [currentPlaceholder, setCurrentPlaceholder] = createSignal('')
|
|
98
100
|
const [currentDisabled, setCurrentDisabled] = createSignal(false)
|
|
101
|
+
const [currentReadOnly, setCurrentReadOnly] = createSignal(false)
|
|
102
|
+
|
|
103
|
+
if (
|
|
104
|
+
disabledSignal !== undefined &&
|
|
105
|
+
typeof process !== 'undefined' &&
|
|
106
|
+
process.env.NODE_ENV === 'development'
|
|
107
|
+
) {
|
|
108
|
+
console.warn(
|
|
109
|
+
'TextField: `disabledSignal` is deprecated. Prefer `disabled` with a Signal<boolean>.'
|
|
110
|
+
)
|
|
111
|
+
}
|
|
99
112
|
|
|
100
113
|
// Set up reactive updates for signal-based props
|
|
101
114
|
createEffect(() => {
|
|
@@ -120,6 +133,12 @@ export const TextField: Component<TextFieldProps> = props => {
|
|
|
120
133
|
}
|
|
121
134
|
})
|
|
122
135
|
|
|
136
|
+
createEffect(() => {
|
|
137
|
+
if (readOnlySignal) {
|
|
138
|
+
setCurrentReadOnly(resolveValue(readOnlySignal, false))
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
|
|
123
142
|
// Sync with controlled value
|
|
124
143
|
if (controlledValue !== undefined) {
|
|
125
144
|
createEffect(() => {
|
|
@@ -241,7 +260,16 @@ export const TextField: Component<TextFieldProps> = props => {
|
|
|
241
260
|
const currentPlaceholderValue = placeholderSignal
|
|
242
261
|
? currentPlaceholder()
|
|
243
262
|
: placeholder
|
|
244
|
-
const
|
|
263
|
+
const disabledBinding = disabledSignal
|
|
264
|
+
? currentDisabled
|
|
265
|
+
: typeof disabled === 'function'
|
|
266
|
+
? (disabled as () => boolean)
|
|
267
|
+
: () => disabled
|
|
268
|
+
const readOnlyBinding = readOnlySignal
|
|
269
|
+
? currentReadOnly
|
|
270
|
+
: typeof readOnly === 'function'
|
|
271
|
+
? (readOnly as () => boolean)
|
|
272
|
+
: () => readOnly
|
|
245
273
|
const displayValue = textSignal ? currentText() : field.value() || ''
|
|
246
274
|
const formattedDisplayValue = formatValue(displayValue)
|
|
247
275
|
|
|
@@ -251,7 +279,8 @@ export const TextField: Component<TextFieldProps> = props => {
|
|
|
251
279
|
name,
|
|
252
280
|
value: formattedDisplayValue,
|
|
253
281
|
placeholder: currentPlaceholderValue,
|
|
254
|
-
disabled:
|
|
282
|
+
disabled: disabledBinding,
|
|
283
|
+
readOnly: readOnlyBinding,
|
|
255
284
|
required,
|
|
256
285
|
minlength: minLength,
|
|
257
286
|
maxlength: maxLength,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isSignal } from '@tachui/core'
|
|
1
|
+
import { createEffect, isComputed, isSignal } from '@tachui/core'
|
|
2
2
|
import type { Signal } from '@tachui/core'
|
|
3
3
|
import type {
|
|
4
4
|
Modifier,
|
|
@@ -22,6 +22,7 @@ function resolvePlaceholder(value: PlaceholderValue): string {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const placeholderHandlers = new WeakMap<Element, () => void>()
|
|
25
|
+
const placeholderDisposers = new WeakMap<Element, () => void>()
|
|
25
26
|
|
|
26
27
|
function applyPlaceholder(
|
|
27
28
|
element: Element,
|
|
@@ -59,10 +60,21 @@ function createPlaceholderModifier(value: PlaceholderValue): Modifier {
|
|
|
59
60
|
element.removeEventListener('input', previous)
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
const previousDispose = placeholderDisposers.get(element)
|
|
64
|
+
if (previousDispose) {
|
|
65
|
+
previousDispose()
|
|
66
|
+
placeholderDisposers.delete(element)
|
|
67
|
+
}
|
|
68
|
+
|
|
62
69
|
const handler = () => applyPlaceholder(element, value)
|
|
63
70
|
element.addEventListener('input', handler)
|
|
64
71
|
placeholderHandlers.set(element, handler)
|
|
65
72
|
|
|
73
|
+
if (isSignal(value) || isComputed(value)) {
|
|
74
|
+
const effect = createEffect(update)
|
|
75
|
+
placeholderDisposers.set(element, () => effect.dispose())
|
|
76
|
+
}
|
|
77
|
+
|
|
66
78
|
return node
|
|
67
79
|
},
|
|
68
80
|
}
|
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
import type { Modifier, ModifierContext } from '@tachui/core/modifiers/types'
|
|
2
2
|
import type { ModifierRegistry, PluginInfo } from '@tachui/registry'
|
|
3
3
|
import { registerModifierWithMetadata } from '@tachui/core/modifiers'
|
|
4
|
+
import { createEffect, isComputed, isSignal, type Signal } from '@tachui/core'
|
|
4
5
|
|
|
5
6
|
const requiredPriority = 72
|
|
6
7
|
|
|
7
8
|
interface RequiredModifierOptions {
|
|
9
|
+
message?: string | Signal<string>
|
|
10
|
+
enabled?: boolean | Signal<boolean>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type RequiredInput = boolean | string | RequiredModifierOptions
|
|
14
|
+
interface ResolvedRequiredOptions {
|
|
8
15
|
message?: string
|
|
9
16
|
enabled?: boolean
|
|
10
17
|
}
|
|
18
|
+
const requiredDisposers = new WeakMap<Element, () => void>()
|
|
19
|
+
|
|
20
|
+
function resolveReactive<T>(value: T | Signal<T>): T {
|
|
21
|
+
if (isSignal(value) || isComputed(value)) {
|
|
22
|
+
return (value as Signal<T>)()
|
|
23
|
+
}
|
|
24
|
+
return value as T
|
|
25
|
+
}
|
|
11
26
|
|
|
12
27
|
function normalizeOptions(
|
|
13
|
-
value?:
|
|
28
|
+
value?: RequiredInput,
|
|
14
29
|
): RequiredModifierOptions {
|
|
15
30
|
if (typeof value === 'boolean') {
|
|
16
31
|
return { enabled: value }
|
|
@@ -27,9 +42,36 @@ function normalizeOptions(
|
|
|
27
42
|
return normalized
|
|
28
43
|
}
|
|
29
44
|
|
|
45
|
+
function resolveOptions(
|
|
46
|
+
value?: RequiredInput,
|
|
47
|
+
): ResolvedRequiredOptions {
|
|
48
|
+
const normalized = normalizeOptions(value)
|
|
49
|
+
return {
|
|
50
|
+
enabled:
|
|
51
|
+
normalized.enabled === undefined
|
|
52
|
+
? undefined
|
|
53
|
+
: resolveReactive(normalized.enabled),
|
|
54
|
+
message:
|
|
55
|
+
normalized.message === undefined
|
|
56
|
+
? undefined
|
|
57
|
+
: resolveReactive(normalized.message),
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function hasReactiveOptions(value?: RequiredInput): boolean {
|
|
62
|
+
if (isSignal(value) || isComputed(value)) return true
|
|
63
|
+
if (!value || typeof value !== 'object') return false
|
|
64
|
+
return (
|
|
65
|
+
isSignal(value.enabled) ||
|
|
66
|
+
isComputed(value.enabled) ||
|
|
67
|
+
isSignal(value.message) ||
|
|
68
|
+
isComputed(value.message)
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
30
72
|
function applyRequired(
|
|
31
73
|
element: Element,
|
|
32
|
-
{ enabled, message }:
|
|
74
|
+
{ enabled, message }: ResolvedRequiredOptions,
|
|
33
75
|
): void {
|
|
34
76
|
if (!(element instanceof HTMLElement)) return
|
|
35
77
|
|
|
@@ -57,18 +99,30 @@ function applyRequired(
|
|
|
57
99
|
}
|
|
58
100
|
|
|
59
101
|
function createRequiredModifier(
|
|
60
|
-
options?:
|
|
102
|
+
options?: RequiredInput,
|
|
61
103
|
): Modifier {
|
|
62
|
-
const normalized = normalizeOptions(options)
|
|
63
104
|
return {
|
|
64
105
|
type: 'forms:required',
|
|
65
106
|
priority: requiredPriority,
|
|
66
|
-
properties:
|
|
107
|
+
properties: normalizeOptions(options),
|
|
67
108
|
apply(node: any, context: ModifierContext) {
|
|
68
109
|
const element = (context.element ?? node) as Element | undefined
|
|
69
110
|
if (!element) return node
|
|
70
111
|
|
|
71
|
-
applyRequired(element,
|
|
112
|
+
const applyCurrent = () => applyRequired(element, resolveOptions(options))
|
|
113
|
+
|
|
114
|
+
const previousDispose = requiredDisposers.get(element)
|
|
115
|
+
if (previousDispose) {
|
|
116
|
+
previousDispose()
|
|
117
|
+
requiredDisposers.delete(element)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
applyCurrent()
|
|
121
|
+
|
|
122
|
+
if (hasReactiveOptions(options)) {
|
|
123
|
+
const effect = createEffect(applyCurrent)
|
|
124
|
+
requiredDisposers.set(element, () => effect.dispose())
|
|
125
|
+
}
|
|
72
126
|
return node
|
|
73
127
|
},
|
|
74
128
|
}
|
|
@@ -83,7 +137,7 @@ const REQUIRED_METADATA = {
|
|
|
83
137
|
}
|
|
84
138
|
|
|
85
139
|
export function required(
|
|
86
|
-
options?:
|
|
140
|
+
options?: RequiredInput,
|
|
87
141
|
): Modifier {
|
|
88
142
|
return createRequiredModifier(options)
|
|
89
143
|
}
|
|
@@ -92,7 +146,7 @@ export function registerRequiredModifier(
|
|
|
92
146
|
registry?: ModifierRegistry,
|
|
93
147
|
plugin?: PluginInfo,
|
|
94
148
|
): void {
|
|
95
|
-
const factory = (options?:
|
|
149
|
+
const factory = (options?: RequiredInput) =>
|
|
96
150
|
createRequiredModifier(options)
|
|
97
151
|
|
|
98
152
|
registerModifierWithMetadata(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Modifier, ModifierContext } from '@tachui/core/modifiers/types'
|
|
2
2
|
import type { ModifierRegistry, PluginInfo } from '@tachui/registry'
|
|
3
3
|
import { registerModifierWithMetadata } from '@tachui/core/modifiers'
|
|
4
|
+
import { createEffect, isComputed, isSignal, type Signal } from '@tachui/core'
|
|
4
5
|
import { validateValue } from '../validation'
|
|
5
6
|
import type { ValidationResult, ValidationRule } from '../types'
|
|
6
7
|
|
|
@@ -9,25 +10,40 @@ const validationPriority = 74
|
|
|
9
10
|
type ValidationArgs =
|
|
10
11
|
| ValidationRule[]
|
|
11
12
|
| ValidationRule
|
|
13
|
+
| Signal<ValidationRule[]>
|
|
14
|
+
| Signal<ValidationRule>
|
|
15
|
+
| Signal<ValidationRule[] | ValidationRule>
|
|
12
16
|
|
|
13
17
|
interface ValidationProperties {
|
|
14
18
|
rules: ValidationRule[]
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
const validationHandlers = new WeakMap<Element, (event?: Event) => void>()
|
|
22
|
+
const validationDisposers = new WeakMap<Element, () => void>()
|
|
23
|
+
|
|
24
|
+
function isReactiveRuleInput(
|
|
25
|
+
value: ValidationArgs
|
|
26
|
+
): value is Signal<ValidationRule[] | ValidationRule> {
|
|
27
|
+
return isSignal(value) || isComputed(value)
|
|
28
|
+
}
|
|
18
29
|
|
|
19
30
|
function normalizeRules(input: ValidationArgs[]): ValidationRule[] {
|
|
20
31
|
const flattened: ValidationRule[] = []
|
|
21
32
|
input.forEach(entry => {
|
|
22
|
-
|
|
23
|
-
|
|
33
|
+
const resolvedEntry = isReactiveRuleInput(entry) ? entry() : entry
|
|
34
|
+
if (Array.isArray(resolvedEntry)) {
|
|
35
|
+
flattened.push(...resolvedEntry)
|
|
24
36
|
} else {
|
|
25
|
-
flattened.push(
|
|
37
|
+
flattened.push(resolvedEntry)
|
|
26
38
|
}
|
|
27
39
|
})
|
|
28
40
|
return flattened
|
|
29
41
|
}
|
|
30
42
|
|
|
43
|
+
function hasReactiveRuleInput(input: ValidationArgs[]): boolean {
|
|
44
|
+
return input.some(entry => isReactiveRuleInput(entry))
|
|
45
|
+
}
|
|
46
|
+
|
|
31
47
|
function applyValidationResult(
|
|
32
48
|
element: Element,
|
|
33
49
|
result: ValidationResult,
|
|
@@ -73,27 +89,42 @@ function createValidationHandler(
|
|
|
73
89
|
}
|
|
74
90
|
|
|
75
91
|
function createValidationModifier(
|
|
76
|
-
|
|
92
|
+
rulesInput: ValidationArgs[],
|
|
77
93
|
): Modifier {
|
|
78
|
-
const normalizedRules = rules.length
|
|
79
|
-
? rules
|
|
80
|
-
: (['required'] as ValidationRule[])
|
|
81
|
-
|
|
82
94
|
return {
|
|
83
95
|
type: 'forms:validation',
|
|
84
96
|
priority: validationPriority,
|
|
85
|
-
properties: {
|
|
97
|
+
properties: {
|
|
98
|
+
get rules() {
|
|
99
|
+
const resolved = normalizeRules(rulesInput)
|
|
100
|
+
return resolved.length ? resolved : (['required'] as ValidationRule[])
|
|
101
|
+
},
|
|
102
|
+
} as ValidationProperties,
|
|
86
103
|
apply(node: any, context: ModifierContext) {
|
|
87
104
|
const element = (context.element ?? node) as Element | undefined
|
|
88
105
|
if (!element) return node
|
|
89
106
|
|
|
90
|
-
const
|
|
107
|
+
const buildRules = (): ValidationRule[] => {
|
|
108
|
+
const resolved = normalizeRules(rulesInput)
|
|
109
|
+
return resolved.length ? resolved : (['required'] as ValidationRule[])
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const handler = () => {
|
|
113
|
+
const currentRules = buildRules()
|
|
114
|
+
const run = createValidationHandler(element, currentRules)
|
|
115
|
+
run()
|
|
116
|
+
}
|
|
91
117
|
|
|
92
118
|
const existing = validationHandlers.get(element)
|
|
93
119
|
if (existing) {
|
|
94
120
|
element.removeEventListener('blur', existing)
|
|
95
121
|
element.removeEventListener('input', existing)
|
|
96
122
|
}
|
|
123
|
+
const existingDispose = validationDisposers.get(element)
|
|
124
|
+
if (existingDispose) {
|
|
125
|
+
existingDispose()
|
|
126
|
+
validationDisposers.delete(element)
|
|
127
|
+
}
|
|
97
128
|
|
|
98
129
|
element.addEventListener('blur', handler)
|
|
99
130
|
element.addEventListener('input', handler)
|
|
@@ -101,6 +132,11 @@ function createValidationModifier(
|
|
|
101
132
|
|
|
102
133
|
handler()
|
|
103
134
|
|
|
135
|
+
if (hasReactiveRuleInput(rulesInput)) {
|
|
136
|
+
const effect = createEffect(handler)
|
|
137
|
+
validationDisposers.set(element, () => effect.dispose())
|
|
138
|
+
}
|
|
139
|
+
|
|
104
140
|
return node
|
|
105
141
|
},
|
|
106
142
|
}
|
|
@@ -117,8 +153,7 @@ const VALIDATION_METADATA = {
|
|
|
117
153
|
export function validation(
|
|
118
154
|
...rules: ValidationArgs[]
|
|
119
155
|
): Modifier {
|
|
120
|
-
|
|
121
|
-
return createValidationModifier(normalized)
|
|
156
|
+
return createValidationModifier(rules)
|
|
122
157
|
}
|
|
123
158
|
|
|
124
159
|
export function registerValidationModifier(
|
|
@@ -126,7 +161,7 @@ export function registerValidationModifier(
|
|
|
126
161
|
plugin?: PluginInfo,
|
|
127
162
|
): void {
|
|
128
163
|
const factory = (...rules: ValidationArgs[]) =>
|
|
129
|
-
createValidationModifier(
|
|
164
|
+
createValidationModifier(rules)
|
|
130
165
|
|
|
131
166
|
registerModifierWithMetadata(
|
|
132
167
|
'validation',
|
package/src/types/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
ComponentChildren,
|
|
10
10
|
ComponentInstance,
|
|
11
11
|
ComponentProps,
|
|
12
|
+
Signal,
|
|
12
13
|
} from '@tachui/core'
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -125,7 +126,8 @@ export interface BaseFieldProps extends ComponentProps {
|
|
|
125
126
|
name: string
|
|
126
127
|
label?: string
|
|
127
128
|
placeholder?: string
|
|
128
|
-
disabled?: boolean
|
|
129
|
+
disabled?: boolean | Signal<boolean>
|
|
130
|
+
readOnly?: boolean | Signal<boolean>
|
|
129
131
|
required?: boolean
|
|
130
132
|
validation?: FieldValidation
|
|
131
133
|
value?: any
|
|
@@ -241,6 +243,7 @@ export interface TextFieldProps extends BaseFieldProps {
|
|
|
241
243
|
text?: string | (() => string) // Signal support
|
|
242
244
|
placeholderSignal?: string | (() => string) // Signal support
|
|
243
245
|
disabledSignal?: boolean | (() => boolean) // Signal support
|
|
246
|
+
readOnlySignal?: boolean | (() => boolean) // Signal support
|
|
244
247
|
}
|
|
245
248
|
|
|
246
249
|
/**
|