@vielzeug/refine 1.3.0 → 1.4.0
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/button.cjs +1 -1
- package/dist/button.cjs.map +1 -1
- package/dist/button.js +3 -3
- package/dist/button.js.map +1 -1
- package/dist/checkbox-group.cjs +7 -7
- package/dist/checkbox-group.cjs.map +1 -1
- package/dist/checkbox-group.js +35 -26
- package/dist/checkbox-group.js.map +1 -1
- package/dist/checkbox.cjs +3 -3
- package/dist/checkbox.cjs.map +1 -1
- package/dist/checkbox.js +24 -19
- package/dist/checkbox.js.map +1 -1
- package/dist/combobox.cjs +24 -24
- package/dist/combobox.cjs.map +1 -1
- package/dist/combobox.js +133 -129
- package/dist/combobox.js.map +1 -1
- package/dist/content/card/card.css.cjs +1 -1
- package/dist/content/card/card.css.cjs.map +1 -1
- package/dist/content/card/card.css.js +1 -1
- package/dist/content/card/card.css.js.map +1 -1
- package/dist/content/icon/icon.css.cjs +1 -1
- package/dist/content/icon/icon.css.cjs.map +1 -1
- package/dist/content/icon/icon.css.js +1 -1
- package/dist/content/icon/icon.css.js.map +1 -1
- package/dist/custom-elements.json +4711 -4209
- package/dist/feedback/toast/toast.css.cjs +1 -1
- package/dist/feedback/toast/toast.css.cjs.map +1 -1
- package/dist/feedback/toast/toast.css.js +1 -1
- package/dist/feedback/toast/toast.css.js.map +1 -1
- package/dist/headless/auto-resize.cjs +2 -0
- package/dist/headless/auto-resize.cjs.map +1 -0
- package/dist/headless/auto-resize.d.ts +29 -0
- package/dist/headless/auto-resize.d.ts.map +1 -0
- package/dist/headless/auto-resize.js +16 -0
- package/dist/headless/auto-resize.js.map +1 -0
- package/dist/headless/checkable.cjs +1 -1
- package/dist/headless/checkable.cjs.map +1 -1
- package/dist/headless/checkable.d.ts +25 -7
- package/dist/headless/checkable.d.ts.map +1 -1
- package/dist/headless/checkable.js +19 -14
- package/dist/headless/checkable.js.map +1 -1
- package/dist/headless/choice-field.cjs +1 -1
- package/dist/headless/choice-field.cjs.map +1 -1
- package/dist/headless/choice-field.d.ts +21 -0
- package/dist/headless/choice-field.d.ts.map +1 -1
- package/dist/headless/choice-field.js +27 -19
- package/dist/headless/choice-field.js.map +1 -1
- package/dist/headless/composer.cjs +2 -0
- package/dist/headless/composer.cjs.map +1 -0
- package/dist/headless/composer.d.ts +44 -0
- package/dist/headless/composer.d.ts.map +1 -0
- package/dist/headless/composer.js +21 -0
- package/dist/headless/composer.js.map +1 -0
- package/dist/headless/field-base.cjs +1 -1
- package/dist/headless/field-base.cjs.map +1 -1
- package/dist/headless/field-base.d.ts +97 -84
- package/dist/headless/field-base.d.ts.map +1 -1
- package/dist/headless/field-base.js +43 -32
- package/dist/headless/field-base.js.map +1 -1
- package/dist/headless/index.d.ts +3 -1
- package/dist/headless/index.d.ts.map +1 -1
- package/dist/headless/text-field.cjs +1 -1
- package/dist/headless/text-field.cjs.map +1 -1
- package/dist/headless/text-field.d.ts +40 -0
- package/dist/headless/text-field.d.ts.map +1 -1
- package/dist/headless/text-field.js +30 -25
- package/dist/headless/text-field.js.map +1 -1
- package/dist/headless.cjs +1 -1
- package/dist/headless.js +27 -25
- package/dist/index.cjs +1 -1
- package/dist/index.js +61 -60
- package/dist/input.cjs +9 -9
- package/dist/input.cjs.map +1 -1
- package/dist/input.js +49 -44
- package/dist/input.js.map +1 -1
- package/dist/inputs/button/button.css.cjs +1 -1
- package/dist/inputs/button/button.css.cjs.map +1 -1
- package/dist/inputs/button/button.css.js +1 -1
- package/dist/inputs/button/button.css.js.map +1 -1
- package/dist/inputs/checkbox/checkbox.css.cjs +1 -1
- package/dist/inputs/checkbox/checkbox.css.cjs.map +1 -1
- package/dist/inputs/checkbox/checkbox.css.js +1 -1
- package/dist/inputs/checkbox/checkbox.css.js.map +1 -1
- package/dist/inputs/checkbox/checkbox.d.ts +3 -0
- package/dist/inputs/checkbox/checkbox.d.ts.map +1 -1
- package/dist/inputs/checkbox-group/checkbox-group.css.cjs +1 -1
- package/dist/inputs/checkbox-group/checkbox-group.css.cjs.map +1 -1
- package/dist/inputs/checkbox-group/checkbox-group.css.js +1 -1
- package/dist/inputs/checkbox-group/checkbox-group.css.js.map +1 -1
- package/dist/inputs/combobox/combobox.d.ts +1 -0
- package/dist/inputs/combobox/combobox.d.ts.map +1 -1
- package/dist/inputs/combobox/combobox.types.d.ts +2 -0
- package/dist/inputs/combobox/combobox.types.d.ts.map +1 -1
- package/dist/inputs/file-input/file-input.css.cjs +1 -1
- package/dist/inputs/file-input/file-input.css.cjs.map +1 -1
- package/dist/inputs/file-input/file-input.css.js +1 -1
- package/dist/inputs/file-input/file-input.css.js.map +1 -1
- package/dist/inputs/index.d.ts +2 -0
- package/dist/inputs/index.d.ts.map +1 -1
- package/dist/inputs/input/input.css.cjs +1 -1
- package/dist/inputs/input/input.css.cjs.map +1 -1
- package/dist/inputs/input/input.css.js +1 -1
- package/dist/inputs/input/input.css.js.map +1 -1
- package/dist/inputs/input/input.d.ts.map +1 -1
- package/dist/inputs/message-composer/message-composer.css.cjs +2 -0
- package/dist/inputs/message-composer/message-composer.css.cjs.map +1 -0
- package/dist/inputs/message-composer/message-composer.css.js +6 -0
- package/dist/inputs/message-composer/message-composer.css.js.map +1 -0
- package/dist/inputs/message-composer/message-composer.d.ts +139 -0
- package/dist/inputs/message-composer/message-composer.d.ts.map +1 -0
- package/dist/inputs/otp-input/otp-input.css.cjs +1 -1
- package/dist/inputs/otp-input/otp-input.css.cjs.map +1 -1
- package/dist/inputs/otp-input/otp-input.css.js +1 -1
- package/dist/inputs/otp-input/otp-input.css.js.map +1 -1
- package/dist/inputs/radio/radio.css.cjs +1 -1
- package/dist/inputs/radio/radio.css.cjs.map +1 -1
- package/dist/inputs/radio/radio.css.js +1 -1
- package/dist/inputs/radio/radio.css.js.map +1 -1
- package/dist/inputs/radio-group/radio-group.css.cjs +1 -1
- package/dist/inputs/radio-group/radio-group.css.cjs.map +1 -1
- package/dist/inputs/radio-group/radio-group.css.js +1 -1
- package/dist/inputs/radio-group/radio-group.css.js.map +1 -1
- package/dist/inputs/rating/rating.css.cjs +1 -1
- package/dist/inputs/rating/rating.css.cjs.map +1 -1
- package/dist/inputs/rating/rating.css.js +1 -1
- package/dist/inputs/rating/rating.css.js.map +1 -1
- package/dist/inputs/shared/field-binding.cjs +1 -1
- package/dist/inputs/shared/field-binding.cjs.map +1 -1
- package/dist/inputs/shared/field-binding.d.ts +15 -0
- package/dist/inputs/shared/field-binding.d.ts.map +1 -1
- package/dist/inputs/shared/field-binding.js +4 -3
- package/dist/inputs/shared/field-binding.js.map +1 -1
- package/dist/inputs/shared/form-context.cjs.map +1 -1
- package/dist/inputs/shared/form-context.d.ts +6 -0
- package/dist/inputs/shared/form-context.d.ts.map +1 -1
- package/dist/inputs/shared/form-context.js.map +1 -1
- package/dist/inputs/slider/slider.css.cjs +1 -1
- package/dist/inputs/slider/slider.css.cjs.map +1 -1
- package/dist/inputs/slider/slider.css.js +1 -1
- package/dist/inputs/slider/slider.css.js.map +1 -1
- package/dist/inputs/switch/switch.css.cjs +1 -1
- package/dist/inputs/switch/switch.css.cjs.map +1 -1
- package/dist/inputs/switch/switch.css.js +1 -1
- package/dist/inputs/switch/switch.css.js.map +1 -1
- package/dist/inputs/textarea/textarea.css.cjs +1 -1
- package/dist/inputs/textarea/textarea.css.cjs.map +1 -1
- package/dist/inputs/textarea/textarea.css.js +1 -1
- package/dist/inputs/textarea/textarea.css.js.map +1 -1
- package/dist/inputs/textarea/textarea.d.ts.map +1 -1
- package/dist/layout/navbar/navbar.css.cjs +1 -1
- package/dist/layout/navbar/navbar.css.cjs.map +1 -1
- package/dist/layout/navbar/navbar.css.js +1 -1
- package/dist/layout/navbar/navbar.css.js.map +1 -1
- package/dist/layout/sidebar/sidebar.css.cjs +1 -1
- package/dist/layout/sidebar/sidebar.css.cjs.map +1 -1
- package/dist/layout/sidebar/sidebar.css.js +1 -1
- package/dist/layout/sidebar/sidebar.css.js.map +1 -1
- package/dist/message-composer.cjs +52 -0
- package/dist/message-composer.cjs.map +1 -0
- package/dist/message-composer.js +168 -0
- package/dist/message-composer.js.map +1 -0
- package/dist/ore/dist/base-element.cjs +1 -1
- package/dist/ore/dist/base-element.cjs.map +1 -1
- package/dist/ore/dist/base-element.js +13 -3
- package/dist/ore/dist/base-element.js.map +1 -1
- package/dist/ore/dist/forms/field.cjs +1 -1
- package/dist/ore/dist/forms/field.cjs.map +1 -1
- package/dist/ore/dist/forms/field.js +29 -22
- package/dist/ore/dist/forms/field.js.map +1 -1
- package/dist/ore/dist/runtime.cjs +1 -1
- package/dist/ore/dist/runtime.cjs.map +1 -1
- package/dist/ore/dist/runtime.js +3 -1
- package/dist/ore/dist/runtime.js.map +1 -1
- package/dist/overlay/menu/menu.css.cjs +1 -1
- package/dist/overlay/menu/menu.css.cjs.map +1 -1
- package/dist/overlay/menu/menu.css.js +1 -1
- package/dist/overlay/menu/menu.css.js.map +1 -1
- package/dist/radio-group.cjs +9 -9
- package/dist/radio-group.cjs.map +1 -1
- package/dist/radio-group.js +44 -38
- package/dist/radio-group.js.map +1 -1
- package/dist/radio.cjs +3 -3
- package/dist/radio.cjs.map +1 -1
- package/dist/radio.js +23 -24
- package/dist/radio.js.map +1 -1
- package/dist/refine.cjs +228 -181
- package/dist/refine.cjs.map +1 -1
- package/dist/refine.iife.js +228 -181
- package/dist/refine.iife.js.map +1 -1
- package/dist/refine.js +251 -204
- package/dist/refine.js.map +1 -1
- package/dist/select.cjs +31 -31
- package/dist/select.cjs.map +1 -1
- package/dist/select.js +125 -122
- package/dist/select.js.map +1 -1
- package/dist/shared/size-presets.cjs +1 -1
- package/dist/shared/size-presets.cjs.map +1 -1
- package/dist/shared/size-presets.d.ts +23 -0
- package/dist/shared/size-presets.d.ts.map +1 -1
- package/dist/shared/size-presets.js +17 -1
- package/dist/shared/size-presets.js.map +1 -1
- package/dist/styles/mixins/animation.css.cjs +0 -3
- package/dist/styles/mixins/animation.css.cjs.map +1 -1
- package/dist/styles/mixins/animation.css.d.ts +12 -4
- package/dist/styles/mixins/animation.css.d.ts.map +1 -1
- package/dist/styles/mixins/animation.css.js +0 -3
- package/dist/styles/mixins/animation.css.js.map +1 -1
- package/dist/styles/mixins/variants.css.cjs.map +1 -1
- package/dist/styles/mixins/variants.css.d.ts +7 -2
- package/dist/styles/mixins/variants.css.d.ts.map +1 -1
- package/dist/styles/mixins/variants.css.js.map +1 -1
- package/dist/styles/theme.css +341 -132
- package/dist/switch.cjs +3 -3
- package/dist/switch.cjs.map +1 -1
- package/dist/switch.js +17 -18
- package/dist/switch.js.map +1 -1
- package/dist/textarea.cjs +15 -15
- package/dist/textarea.cjs.map +1 -1
- package/dist/textarea.js +64 -60
- package/dist/textarea.js.map +1 -1
- package/package.json +21 -9
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
let e=require("@vielzeug/ripple");var t=e=>!e||e.trim()===``,n=n=>{let r=(0,e.computed)(()=>!t(n.value.value)&&!n.disabled?.value&&!n.loading?.value),i=e=>{r.value&&n.onSend(e)};return{canSend:r,handleKeydown:e=>{if(e.key!==`Enter`||e.isComposing)return;let t=n.sendShortcut?.value??`enter`,r=e.metaKey||e.ctrlKey;(t===`mod+enter`?!r:e.shiftKey)||(e.preventDefault(),i(e))},keyShortcutsHint:(0,e.computed)(()=>(n.sendShortcut?.value??`enter`)===`mod+enter`?`Control+Enter Meta+Enter`:`Enter`),send:i}};exports.createComposerControl=n;
|
|
2
|
+
//# sourceMappingURL=composer.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composer.cjs","names":[],"sources":["../../src/headless/composer.ts"],"sourcesContent":["import type { Readable } from '@vielzeug/ripple';\n\nimport { computed } from '@vielzeug/ripple';\n\n/**\n * Keyboard shortcut that triggers a send:\n * - `'enter'` (default) — Enter sends, Shift+Enter inserts a newline.\n * - `'mod+enter'` — Enter always inserts a newline; Ctrl/Cmd+Enter sends.\n */\nexport type SendShortcut = 'enter' | 'mod+enter';\n\nexport type ComposerControlOptions = {\n disabled?: Readable<boolean | undefined>;\n /** Blocks further sends (e.g. a send is already in flight) without disabling editing. */\n loading?: Readable<boolean | undefined>;\n /** Called for a send attempt that passed the `canSend` guard. */\n onSend: (event: KeyboardEvent | MouseEvent) => void;\n sendShortcut?: Readable<SendShortcut | undefined>;\n value: Readable<string | undefined>;\n};\n\nexport type ComposerControl = {\n /** Whether the current value is non-blank and the control isn't disabled/loading. */\n canSend: Readable<boolean>;\n /** Wire onto the raw `<textarea>`'s `keydown` — intercepts the resolved send shortcut. */\n handleKeydown: (event: KeyboardEvent) => void;\n /** `aria-keyshortcuts` value describing the resolved shortcut. */\n keyShortcutsHint: Readable<string>;\n /** Attempts a send; no-ops when `canSend` is false. Wire onto the send button's `click`. */\n send: (event: KeyboardEvent | MouseEvent) => void;\n};\n\nconst isBlank = (value: string | undefined): boolean => !value || value.trim() === '';\n\n/**\n * Send-gesture logic for a message/comment composer — decides *when* a send is allowed\n * and *whether* a given keydown counts as one. Owns no DOM, value state, or event dispatch;\n * the component wires `handleKeydown`/`send` and decides what a send actually does.\n *\n * @example\n * ```ts\n * const composer = createComposerControl({\n * onSend: (event) => attemptSend(event),\n * sendShortcut: props['send-shortcut'],\n * value,\n * });\n * // <textarea @keydown=\"${composer.handleKeydown}\" aria-keyshortcuts=\"${composer.keyShortcutsHint}\">\n * // <button ?disabled=\"${() => !composer.canSend.value}\" @click=\"${composer.send}\">\n * ```\n */\nexport const createComposerControl = (options: ComposerControlOptions): ComposerControl => {\n const canSend = computed(() => !isBlank(options.value.value) && !options.disabled?.value && !options.loading?.value);\n\n const send = (event: KeyboardEvent | MouseEvent): void => {\n if (!canSend.value) return;\n\n options.onSend(event);\n };\n\n const handleKeydown = (event: KeyboardEvent): void => {\n // A composing IME candidate selection also fires `Enter` — never treat that as a send.\n if (event.key !== 'Enter' || event.isComposing) return;\n\n const mode = options.sendShortcut?.value ?? 'enter';\n const hasModifier = event.metaKey || event.ctrlKey;\n\n if (mode === 'mod+enter' ? !hasModifier : event.shiftKey) return;\n\n event.preventDefault();\n send(event);\n };\n\n const keyShortcutsHint = computed(() =>\n (options.sendShortcut?.value ?? 'enter') === 'mod+enter' ? 'Control+Enter Meta+Enter' : 'Enter',\n );\n\n return { canSend, handleKeydown, keyShortcutsHint, send };\n};\n"],"mappings":"kCAgCA,IAAM,EAAW,GAAuC,CAAC,GAAS,EAAM,KAAK,IAAM,GAkBtE,EAAyB,GAAqD,CACzF,IAAM,GAAA,EAAA,EAAA,SAAA,KAAyB,CAAC,EAAQ,EAAQ,MAAM,KAAK,GAAK,CAAC,EAAQ,UAAU,OAAS,CAAC,EAAQ,SAAS,KAAK,EAE7G,EAAQ,GAA4C,CACnD,EAAQ,OAEb,EAAQ,OAAO,CAAK,CACtB,EAmBA,MAAO,CAAE,UAAS,cAjBK,GAA+B,CAEpD,GAAI,EAAM,MAAQ,SAAW,EAAM,YAAa,OAEhD,IAAM,EAAO,EAAQ,cAAc,OAAS,QACtC,EAAc,EAAM,SAAW,EAAM,SAEvC,IAAS,YAAc,CAAC,EAAc,EAAM,YAEhD,EAAM,eAAe,EACrB,EAAK,CAAK,EACZ,EAMiC,kBAAA,EAAA,EAAA,SAAA,MAH9B,EAAQ,cAAc,OAAS,WAAa,YAAc,2BAA6B,OAGzD,EAAkB,MAAK,CAC1D"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Readable } from '@vielzeug/ripple';
|
|
2
|
+
/**
|
|
3
|
+
* Keyboard shortcut that triggers a send:
|
|
4
|
+
* - `'enter'` (default) — Enter sends, Shift+Enter inserts a newline.
|
|
5
|
+
* - `'mod+enter'` — Enter always inserts a newline; Ctrl/Cmd+Enter sends.
|
|
6
|
+
*/
|
|
7
|
+
export type SendShortcut = 'enter' | 'mod+enter';
|
|
8
|
+
export type ComposerControlOptions = {
|
|
9
|
+
disabled?: Readable<boolean | undefined>;
|
|
10
|
+
/** Blocks further sends (e.g. a send is already in flight) without disabling editing. */
|
|
11
|
+
loading?: Readable<boolean | undefined>;
|
|
12
|
+
/** Called for a send attempt that passed the `canSend` guard. */
|
|
13
|
+
onSend: (event: KeyboardEvent | MouseEvent) => void;
|
|
14
|
+
sendShortcut?: Readable<SendShortcut | undefined>;
|
|
15
|
+
value: Readable<string | undefined>;
|
|
16
|
+
};
|
|
17
|
+
export type ComposerControl = {
|
|
18
|
+
/** Whether the current value is non-blank and the control isn't disabled/loading. */
|
|
19
|
+
canSend: Readable<boolean>;
|
|
20
|
+
/** Wire onto the raw `<textarea>`'s `keydown` — intercepts the resolved send shortcut. */
|
|
21
|
+
handleKeydown: (event: KeyboardEvent) => void;
|
|
22
|
+
/** `aria-keyshortcuts` value describing the resolved shortcut. */
|
|
23
|
+
keyShortcutsHint: Readable<string>;
|
|
24
|
+
/** Attempts a send; no-ops when `canSend` is false. Wire onto the send button's `click`. */
|
|
25
|
+
send: (event: KeyboardEvent | MouseEvent) => void;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Send-gesture logic for a message/comment composer — decides *when* a send is allowed
|
|
29
|
+
* and *whether* a given keydown counts as one. Owns no DOM, value state, or event dispatch;
|
|
30
|
+
* the component wires `handleKeydown`/`send` and decides what a send actually does.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* const composer = createComposerControl({
|
|
35
|
+
* onSend: (event) => attemptSend(event),
|
|
36
|
+
* sendShortcut: props['send-shortcut'],
|
|
37
|
+
* value,
|
|
38
|
+
* });
|
|
39
|
+
* // <textarea @keydown="${composer.handleKeydown}" aria-keyshortcuts="${composer.keyShortcutsHint}">
|
|
40
|
+
* // <button ?disabled="${() => !composer.canSend.value}" @click="${composer.send}">
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare const createComposerControl: (options: ComposerControlOptions) => ComposerControl;
|
|
44
|
+
//# sourceMappingURL=composer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composer.d.ts","sourceRoot":"","sources":["../../src/headless/composer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAIjD;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,WAAW,CAAC;AAEjD,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACzC,yFAAyF;IACzF,OAAO,CAAC,EAAE,QAAQ,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACxC,iEAAiE;IACjE,MAAM,EAAE,CAAC,KAAK,EAAE,aAAa,GAAG,UAAU,KAAK,IAAI,CAAC;IACpD,YAAY,CAAC,EAAE,QAAQ,CAAC,YAAY,GAAG,SAAS,CAAC,CAAC;IAClD,KAAK,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,qFAAqF;IACrF,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC3B,0FAA0F;IAC1F,aAAa,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC9C,kEAAkE;IAClE,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACnC,4FAA4F;IAC5F,IAAI,EAAE,CAAC,KAAK,EAAE,aAAa,GAAG,UAAU,KAAK,IAAI,CAAC;CACnD,CAAC;AAIF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,qBAAqB,GAAI,SAAS,sBAAsB,KAAG,eA2BvE,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { computed as e } from "@vielzeug/ripple";
|
|
2
|
+
//#region src/headless/composer.ts
|
|
3
|
+
var t = (e) => !e || e.trim() === "", n = (n) => {
|
|
4
|
+
let r = e(() => !t(n.value.value) && !n.disabled?.value && !n.loading?.value), i = (e) => {
|
|
5
|
+
r.value && n.onSend(e);
|
|
6
|
+
};
|
|
7
|
+
return {
|
|
8
|
+
canSend: r,
|
|
9
|
+
handleKeydown: (e) => {
|
|
10
|
+
if (e.key !== "Enter" || e.isComposing) return;
|
|
11
|
+
let t = n.sendShortcut?.value ?? "enter", r = e.metaKey || e.ctrlKey;
|
|
12
|
+
(t === "mod+enter" ? !r : e.shiftKey) || (e.preventDefault(), i(e));
|
|
13
|
+
},
|
|
14
|
+
keyShortcutsHint: e(() => (n.sendShortcut?.value ?? "enter") === "mod+enter" ? "Control+Enter Meta+Enter" : "Enter"),
|
|
15
|
+
send: i
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
export { n as createComposerControl };
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=composer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composer.js","names":[],"sources":["../../src/headless/composer.ts"],"sourcesContent":["import type { Readable } from '@vielzeug/ripple';\n\nimport { computed } from '@vielzeug/ripple';\n\n/**\n * Keyboard shortcut that triggers a send:\n * - `'enter'` (default) — Enter sends, Shift+Enter inserts a newline.\n * - `'mod+enter'` — Enter always inserts a newline; Ctrl/Cmd+Enter sends.\n */\nexport type SendShortcut = 'enter' | 'mod+enter';\n\nexport type ComposerControlOptions = {\n disabled?: Readable<boolean | undefined>;\n /** Blocks further sends (e.g. a send is already in flight) without disabling editing. */\n loading?: Readable<boolean | undefined>;\n /** Called for a send attempt that passed the `canSend` guard. */\n onSend: (event: KeyboardEvent | MouseEvent) => void;\n sendShortcut?: Readable<SendShortcut | undefined>;\n value: Readable<string | undefined>;\n};\n\nexport type ComposerControl = {\n /** Whether the current value is non-blank and the control isn't disabled/loading. */\n canSend: Readable<boolean>;\n /** Wire onto the raw `<textarea>`'s `keydown` — intercepts the resolved send shortcut. */\n handleKeydown: (event: KeyboardEvent) => void;\n /** `aria-keyshortcuts` value describing the resolved shortcut. */\n keyShortcutsHint: Readable<string>;\n /** Attempts a send; no-ops when `canSend` is false. Wire onto the send button's `click`. */\n send: (event: KeyboardEvent | MouseEvent) => void;\n};\n\nconst isBlank = (value: string | undefined): boolean => !value || value.trim() === '';\n\n/**\n * Send-gesture logic for a message/comment composer — decides *when* a send is allowed\n * and *whether* a given keydown counts as one. Owns no DOM, value state, or event dispatch;\n * the component wires `handleKeydown`/`send` and decides what a send actually does.\n *\n * @example\n * ```ts\n * const composer = createComposerControl({\n * onSend: (event) => attemptSend(event),\n * sendShortcut: props['send-shortcut'],\n * value,\n * });\n * // <textarea @keydown=\"${composer.handleKeydown}\" aria-keyshortcuts=\"${composer.keyShortcutsHint}\">\n * // <button ?disabled=\"${() => !composer.canSend.value}\" @click=\"${composer.send}\">\n * ```\n */\nexport const createComposerControl = (options: ComposerControlOptions): ComposerControl => {\n const canSend = computed(() => !isBlank(options.value.value) && !options.disabled?.value && !options.loading?.value);\n\n const send = (event: KeyboardEvent | MouseEvent): void => {\n if (!canSend.value) return;\n\n options.onSend(event);\n };\n\n const handleKeydown = (event: KeyboardEvent): void => {\n // A composing IME candidate selection also fires `Enter` — never treat that as a send.\n if (event.key !== 'Enter' || event.isComposing) return;\n\n const mode = options.sendShortcut?.value ?? 'enter';\n const hasModifier = event.metaKey || event.ctrlKey;\n\n if (mode === 'mod+enter' ? !hasModifier : event.shiftKey) return;\n\n event.preventDefault();\n send(event);\n };\n\n const keyShortcutsHint = computed(() =>\n (options.sendShortcut?.value ?? 'enter') === 'mod+enter' ? 'Control+Enter Meta+Enter' : 'Enter',\n );\n\n return { canSend, handleKeydown, keyShortcutsHint, send };\n};\n"],"mappings":";;AAgCA,IAAM,KAAW,MAAuC,CAAC,KAAS,EAAM,KAAK,MAAM,IAkBtE,KAAyB,MAAqD;CACzF,IAAM,IAAU,QAAe,CAAC,EAAQ,EAAQ,MAAM,KAAK,KAAK,CAAC,EAAQ,UAAU,SAAS,CAAC,EAAQ,SAAS,KAAK,GAE7G,KAAQ,MAA4C;EACnD,EAAQ,SAEb,EAAQ,OAAO,CAAK;CACtB;CAmBA,OAAO;EAAE;EAAS,gBAjBK,MAA+B;GAEpD,IAAI,EAAM,QAAQ,WAAW,EAAM,aAAa;GAEhD,IAAM,IAAO,EAAQ,cAAc,SAAS,SACtC,IAAc,EAAM,WAAW,EAAM;GAEvC,OAAS,cAAc,CAAC,IAAc,EAAM,cAEhD,EAAM,eAAe,GACrB,EAAK,CAAK;EACZ;EAMiC,kBAJR,SACtB,EAAQ,cAAc,SAAS,aAAa,cAAc,6BAA6B,OAGzD;EAAkB;CAAK;AAC1D"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
const e=require("../_dev.cjs");let t=require("@vielzeug/ore"),n=require("@vielzeug/ripple");var r=e=>(0,n.computed)(()=>({errorText:e.error?.value??``,helperText:e.helper?.value??``})),i=e=>(0,n.computed)(()=>{let t=e.value?.value??``,n=e.maxLength?.value,r=Number(n),i=Number.isFinite(r)&&r>0?r:null,a=i!==null,o=a?`${t.length} / ${i}`:``,s=a?t.length/i:0;return{counterAtLimit:a?s>=1:!1,counterNearLimit:a?s>=.9&&s<1:!1,counterText:o}}),a=(e,t=`counter`)=>e?e.counterAtLimit?`${t} at-limit`:e.counterNearLimit?`${t} near-limit`:t:t,o=i=>{let a=(0,n.computed)(()=>!!i.disabled?.value),o=(0,t.createStableId)(`helper`),s=(0,t.createStableId)(`error`),c=r({error:i.error,helper:i.helper}),l=(0,n.computed)(()=>c.value.errorText||c.value.helperText?o:null),u=(0,n.computed)(()=>c.value.errorText?s:null),d=(0,n.computed)(()=>c.value.errorText?`true`:null),f=null;return{ariaDescribedBy:l,ariaErrorMessage:u,ariaInvalid:d,assistiveId:o,attachFormField:e=>{f=e},disabled:a,errorId:s,errorText:(0,n.computed)(()=>c.value.errorText),helperText:(0,n.computed)(()=>c.value.helperText),triggerValidation:t=>{if(i.validateOn?.value===t){if(!f){e.warn(`triggerValidation() called before attachFormField() — validation will not run. See createTextField()/createChoiceField()/createCheckable()'s attachFormField doc comment.`);return}f.reportValidity()}}}},s=e=>{let r=e.id??(0,t.createStableId)(e.prefix??`field`),i=(0,t.createStableId)(`label`),a=e.label??(0,n.computed)(()=>void 0),o=e.hasLabel??(0,n.computed)(()=>!!a.value);return{ariaLabelledBy:(0,n.computed)(()=>o.value?i:null),fieldId:r,labelId:i,labelVisible:o}},c=e=>({...o(e),...s(e)});exports.counterClassName=a,exports.createAssistiveState=o,exports.createCounterState=i,exports.createErrorHelperState=r,exports.createField=c,exports.createLabelState=s;
|
|
2
2
|
//# sourceMappingURL=field-base.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-base.cjs","names":[],"sources":["../../src/headless/field-base.ts"],"sourcesContent":["import { createStableId } from '@vielzeug/ore';\nimport { computed, type Readable } from '@vielzeug/ripple';\n\n// ── Validation / context types ────────────────────────────────────────────────\n\nexport type ValidationTrigger = 'blur' | 'change' | 'input' | 'submit';\nexport type ControlValidationMode = ValidationTrigger | undefined;\n\n// ── Field options + handle ───────────────────────────────────────────────────\n\n/** Options shared by both `createTextField` and `createChoiceField`. */\nexport type FieldOptions = {\n disabled?: Readable<boolean | undefined>;\n error?: Readable<string | undefined>;\n /**\n * Returns the underlying form field object used for validation triggering.\n * Called lazily — safe to return `null` before the first render.\n */\n getFormField?: () => { reportValidity(): void } | null;\n /**\n * Override for label visibility that takes precedence over deriving it from\n * the `label` text signal. Pass a computed signal that checks both prop and\n * slot presence to enable slot-first composition.\n *\n * @example\n * ```ts\n * const hasLabel = computed(() => !!props.label.value || slots.has('label').value);\n * const field = createField({ ...options, hasLabel });\n * ```\n */\n hasLabel?: Readable<boolean>;\n helper?: Readable<string | undefined>;\n /**\n * When provided, used directly as `fieldId` instead of generating one via\n * `createStableId`. Useful in tests for deterministic ID assertions.\n */\n id?: string;\n /**\n * Label text signal. When provided, `labelVisible` and `ariaLabelledBy` are\n * computed reactively from this value (see `hasLabel` for the slot-first override).\n */\n label?: Readable<string | undefined>;\n /**\n * Label placement signal. Defaults to `'inset'`.\n *\n * Not currently consumed by `createField()` itself — visibility is driven\n * solely by `hasLabel`/`label`. Threaded through so component authors can\n * apply placement-specific styling (`label-placement` attribute) without a\n * second prop; if placement-dependent visibility is ever needed, wire it\n * into `labelVisible$` in `createField()` instead of adding a new option.\n */\n labelPlacement?: Readable<LabelPlacement>;\n prefix?: string;\n validateOn?: Readable<ControlValidationMode>;\n};\n\n// ── Error/helper assistive state ──────────────────────────────────────────────\n\nexport type ErrorHelperState = {\n errorText: string;\n helperText: string;\n};\n\nexport type ErrorHelperOptions = {\n error?: Readable<string | undefined>;\n helper?: Readable<string | undefined>;\n};\n\nexport const createErrorHelperState = (options: ErrorHelperOptions): Readable<ErrorHelperState> =>\n computed(() => ({\n errorText: options.error?.value ?? '',\n helperText: options.helper?.value ?? '',\n }));\n\n// ── Counter state (opt-in) ─────────────────────────────────────────────────────\n\n/** Counter state for text fields with `maxLength`. */\nexport type CounterState = {\n counterAtLimit: boolean;\n counterNearLimit: boolean;\n counterText: string;\n};\n\nexport type CounterOptions = {\n maxLength?: Readable<number | undefined>;\n value: Readable<string | undefined>;\n};\n\n/**\n * Creates a reactive counter state signal for text fields with `maxLength`.\n * Only call this when `maxLength` may be set — the state is genuinely opt-in.\n *\n * @example\n * ```ts\n * const counter = createCounterState({ value: tf.value, maxLength: props.maxlength });\n * ```\n */\nexport const createCounterState = (options: CounterOptions): Readable<CounterState> =>\n computed<CounterState>(() => {\n const value = options.value?.value ?? '';\n const maxLength = options.maxLength?.value;\n const parsedMaxLength = Number(maxLength);\n const validMaxLength = Number.isFinite(parsedMaxLength) && parsedMaxLength > 0 ? parsedMaxLength : null;\n const hasCounter = validMaxLength !== null;\n const counterText = hasCounter ? `${value.length} / ${validMaxLength}` : '';\n const ratio = hasCounter ? value.length / validMaxLength : 0;\n\n return {\n counterAtLimit: hasCounter ? ratio >= 1 : false,\n counterNearLimit: hasCounter ? ratio >= 0.9 && ratio < 1 : false,\n counterText,\n };\n });\n\n// ── Label placement ───────────────────────────────────────────────────────────\n\nexport type LabelPlacement = 'inset' | 'outside' | undefined;\n\n// ── Field handle ──────────────────────────────────────────────────────────────\n\n/**\n * Common handle returned by `createField` — the base for both `TextFieldHandle`\n * and `ChoiceFieldHandle`.\n *\n * ARIA signals and label state are flat on the handle — no nested `aria` or\n * `label` sub-objects.\n *\n * @example\n * ```html\n * <label id=\"${labelId}\" ?hidden=\"${() => !labelVisible.value}\">...</label>\n * <input\n * :aria-labelledby=\"${ariaLabelledBy}\"\n * :aria-describedby=\"${ariaDescribedBy}\"\n * :aria-invalid=\"${ariaInvalid}\" />\n * ```\n */\n/** Flat ARIA attribute bag ready to spread into a template bind call. */\nexport type AriaProps = {\n 'aria-describedby': string | null;\n 'aria-errormessage': string | null;\n 'aria-invalid': 'true' | null;\n 'aria-labelledby': string | null;\n};\n\nexport type FieldHandle = {\n /** `aria-describedby` value. Non-null when helper or error text is present. */\n ariaDescribedBy: Readable<string | null>;\n /** `aria-errormessage` value. Non-null when error text is present. */\n ariaErrorMessage: Readable<string | null>;\n /** `aria-invalid` value. `'true'` when error text is present, otherwise `null`. */\n ariaInvalid: Readable<'true' | null>;\n /** `aria-labelledby` value. Non-null when a label is visible. */\n ariaLabelledBy: Readable<string | null>;\n /**\n * Returns a snapshot of the four ARIA attributes as a plain object.\n * Spread into a template bind call to avoid repeating each signal individually.\n *\n * @example\n * ```ts\n * bind(inputEl, { attr: field.ariaProps() });\n * ```\n */\n ariaProps(): AriaProps;\n /**\n * The stable `id` used for `aria-describedby` on the input. Points at the\n * assistive-text region (covers both helper text and error text per WAI-ARIA).\n */\n assistiveId: string;\n disabled: Readable<boolean>;\n /** Stable `id` for the inline error message element (`aria-errormessage`). */\n errorId: string;\n /** Reactive error text. Empty string when no error is set. */\n errorText: Readable<string>;\n fieldId: string;\n /** Reactive helper text. Empty string when no helper is set. */\n helperText: Readable<string>;\n /** Stable `id` for the label element. Stamp this on your `<label id=\"...\">`. */\n labelId: string;\n /** Whether the label should be visible. Use to toggle `hidden` on the `<label>`. */\n labelVisible: Readable<boolean>;\n triggerValidation: (on: Extract<ValidationTrigger, 'blur' | 'change'>) => void;\n};\n\n/**\n * Composes the field triad — IDs, assistive state, and label state — into a\n * single `FieldHandle`. Both `createTextField` and `createChoiceField` build\n * on top of this.\n */\nexport const createField = (options: FieldOptions): FieldHandle => {\n // ── Core ──────────────────────────────────────────────────────────────────\n const disabled = computed(() => Boolean(options.disabled?.value));\n const validateOn = options.validateOn;\n const fieldId = options.id ?? createStableId(options.prefix ?? 'field');\n const assistiveId = createStableId('helper');\n\n const triggerValidation = (on: Extract<ValidationTrigger, 'blur' | 'change'>): void => {\n if (validateOn?.value === on) options.getFormField?.()?.reportValidity();\n };\n\n // ── Label + ARIA state ────────────────────────────────────────────────────\n const labelId = createStableId('label');\n const errorId = createStableId('error');\n\n const label$ = options.label ?? computed(() => undefined);\n\n // Slot-first composition: if the caller provides `hasLabel`, use it instead\n // of deriving visibility purely from the label text signal. This allows\n // components with a `<slot name=\"label\">` to stay visible even when the\n // `label` prop is empty but a slotted label element is present.\n const labelVisible$ = options.hasLabel ?? computed(() => Boolean(label$.value));\n\n const resolvedAssistive = createErrorHelperState({ error: options.error, helper: options.helper });\n\n const ariaDescribedBy = computed(() =>\n resolvedAssistive.value.errorText || resolvedAssistive.value.helperText ? assistiveId : null,\n );\n const ariaErrorMessage = computed(() => (resolvedAssistive.value.errorText ? errorId : null));\n const ariaInvalid = computed<'true' | null>(() => (resolvedAssistive.value.errorText ? 'true' : null));\n const ariaLabelledBy = computed(() => (labelVisible$.value ? labelId : null));\n\n return {\n ariaDescribedBy,\n ariaErrorMessage,\n ariaInvalid,\n ariaLabelledBy,\n ariaProps: () => ({\n 'aria-describedby': ariaDescribedBy.value,\n 'aria-errormessage': ariaErrorMessage.value,\n 'aria-invalid': ariaInvalid.value,\n 'aria-labelledby': ariaLabelledBy.value,\n }),\n assistiveId,\n disabled,\n errorId,\n errorText: computed(() => resolvedAssistive.value.errorText),\n fieldId,\n helperText: computed(() => resolvedAssistive.value.helperText),\n labelId,\n labelVisible: labelVisible$,\n triggerValidation,\n };\n};\n"],"mappings":"6DAoEA,IAAa,EAA0B,IAAA,EAAA,EAAA,SAAA,MACrB,CACd,UAAW,EAAQ,OAAO,OAAS,GACnC,WAAY,EAAQ,QAAQ,OAAS,EACvC,EAAE,EAyBS,EAAsB,IAAA,EAAA,EAAA,SAAA,KACJ,CAC3B,IAAM,EAAQ,EAAQ,OAAO,OAAS,GAChC,EAAY,EAAQ,WAAW,MAC/B,EAAkB,OAAO,CAAS,EAClC,EAAiB,OAAO,SAAS,CAAe,GAAK,EAAkB,EAAI,EAAkB,KAC7F,EAAa,IAAmB,KAChC,EAAc,EAAa,GAAG,EAAM,OAAO,KAAK,IAAmB,GACnE,EAAQ,EAAa,EAAM,OAAS,EAAiB,EAE3D,MAAO,CACL,eAAgB,EAAa,GAAS,EAAI,GAC1C,iBAAkB,EAAa,GAAS,IAAO,EAAQ,EAAI,GAC3D,aACF,CACF,CAAC,EA4EU,EAAe,GAAuC,CAEjE,IAAM,GAAA,EAAA,EAAA,SAAA,KAA0B,EAAQ,EAAQ,UAAU,KAAM,EAC1D,EAAa,EAAQ,WACrB,EAAU,EAAQ,KAAA,EAAA,EAAA,eAAA,CAAqB,EAAQ,QAAU,OAAO,EAChE,GAAA,EAAA,EAAA,eAAA,CAA6B,QAAQ,EAErC,EAAqB,GAA4D,CACjF,GAAY,QAAU,GAAI,EAAQ,eAAe,CAAC,EAAE,eAAe,CACzE,EAGM,GAAA,EAAA,EAAA,eAAA,CAAyB,OAAO,EAChC,GAAA,EAAA,EAAA,eAAA,CAAyB,OAAO,EAEhC,EAAS,EAAQ,QAAA,EAAA,EAAA,SAAA,KAAwB,IAAA,EAAS,EAMlD,EAAgB,EAAQ,WAAA,EAAA,EAAA,SAAA,KAA2B,EAAQ,EAAO,KAAM,EAExE,EAAoB,EAAuB,CAAE,MAAO,EAAQ,MAAO,OAAQ,EAAQ,MAAO,CAAC,EAE3F,GAAA,EAAA,EAAA,SAAA,KACJ,EAAkB,MAAM,WAAa,EAAkB,MAAM,WAAa,EAAc,IAC1F,EACM,GAAA,EAAA,EAAA,SAAA,KAAmC,EAAkB,MAAM,UAAY,EAAU,IAAK,EACtF,GAAA,EAAA,EAAA,SAAA,KAA6C,EAAkB,MAAM,UAAY,OAAS,IAAK,EAC/F,GAAA,EAAA,EAAA,SAAA,KAAiC,EAAc,MAAQ,EAAU,IAAK,EAE5E,MAAO,CACL,kBACA,mBACA,cACA,iBACA,eAAkB,CAChB,mBAAoB,EAAgB,MACpC,oBAAqB,EAAiB,MACtC,eAAgB,EAAY,MAC5B,kBAAmB,EAAe,KACpC,GACA,cACA,WACA,UACA,WAAA,EAAA,EAAA,SAAA,KAA0B,EAAkB,MAAM,SAAS,EAC3D,UACA,YAAA,EAAA,EAAA,SAAA,KAA2B,EAAkB,MAAM,UAAU,EAC7D,UACA,aAAc,EACd,mBACF,CACF"}
|
|
1
|
+
{"version":3,"file":"field-base.cjs","names":[],"sources":["../../src/headless/field-base.ts"],"sourcesContent":["import { createStableId } from '@vielzeug/ore';\nimport { computed, type Readable } from '@vielzeug/ripple';\n\nimport { warn } from '../_dev';\n\n// ── Validation / context types ────────────────────────────────────────────────\n\nexport type ValidationTrigger = 'blur' | 'change' | 'input' | 'submit';\nexport type ControlValidationMode = ValidationTrigger | undefined;\n\n// ── Error/helper assistive state ──────────────────────────────────────────────\n\nexport type ErrorHelperState = {\n errorText: string;\n helperText: string;\n};\n\nexport type ErrorHelperOptions = {\n error?: Readable<string | undefined>;\n helper?: Readable<string | undefined>;\n};\n\nexport const createErrorHelperState = (options: ErrorHelperOptions): Readable<ErrorHelperState> =>\n computed(() => ({\n errorText: options.error?.value ?? '',\n helperText: options.helper?.value ?? '',\n }));\n\n// ── Counter state (opt-in) ─────────────────────────────────────────────────────\n\n/** Counter state for text fields with `maxLength`. */\nexport type CounterState = {\n counterAtLimit: boolean;\n counterNearLimit: boolean;\n counterText: string;\n};\n\nexport type CounterOptions = {\n maxLength?: Readable<number | undefined>;\n value: Readable<string | undefined>;\n};\n\n/**\n * Creates a reactive counter state signal for text fields with `maxLength`.\n * Only call this when `maxLength` may be set — the state is genuinely opt-in.\n *\n * @example\n * ```ts\n * const counter = createCounterState({ value: tf.value, maxLength: props.maxlength });\n * ```\n */\nexport const createCounterState = (options: CounterOptions): Readable<CounterState> =>\n computed<CounterState>(() => {\n const value = options.value?.value ?? '';\n const maxLength = options.maxLength?.value;\n const parsedMaxLength = Number(maxLength);\n const validMaxLength = Number.isFinite(parsedMaxLength) && parsedMaxLength > 0 ? parsedMaxLength : null;\n const hasCounter = validMaxLength !== null;\n const counterText = hasCounter ? `${value.length} / ${validMaxLength}` : '';\n const ratio = hasCounter ? value.length / validMaxLength : 0;\n\n return {\n counterAtLimit: hasCounter ? ratio >= 1 : false,\n counterNearLimit: hasCounter ? ratio >= 0.9 && ratio < 1 : false,\n counterText,\n };\n });\n\n/**\n * The single implementation of \"which modifier class does this counter state map to\" —\n * every field with a character counter (`ore-input`, `ore-textarea`, `ore-message-composer`)\n * renders the identical near-limit/at-limit styling, so this is the one place that decides it.\n *\n * @example\n * ```ts\n * html`<span class=\"${() => counterClassName(counter?.value)}\">...</span>`\n * ```\n */\nexport const counterClassName = (counter: CounterState | undefined, base = 'counter'): string => {\n if (!counter) return base;\n\n if (counter.counterAtLimit) return `${base} at-limit`;\n\n if (counter.counterNearLimit) return `${base} near-limit`;\n\n return base;\n};\n\n// ── Label placement ───────────────────────────────────────────────────────────\n\nexport type LabelPlacement = 'inset' | 'outside' | undefined;\n\n// ── Assistive state (error/helper text, describedby/errormessage/invalid) ─────\n\n/**\n * A field's error/helper/disabled/validation-trigger state — everything an input\n * needs *except* a visible `<label>`. Split out from label state (below) because\n * not every field has one: `ore-message-composer` names itself via `aria-label`\n * (a chat composer's accessible name conventionally comes from context/placeholder,\n * not a floating `<label>`), so it composes this directly instead of pulling in\n * label plumbing it can't use. `createField()` composes both for fields that do\n * render a `<label>`.\n */\nexport type AssistiveStateHandle = {\n /** `aria-describedby` value. Non-null when helper or error text is present. */\n ariaDescribedBy: Readable<string | null>;\n /** `aria-errormessage` value. Non-null when error text is present. */\n ariaErrorMessage: Readable<string | null>;\n /** `aria-invalid` value. `'true'` when error text is present, otherwise `null`. */\n ariaInvalid: Readable<'true' | null>;\n /**\n * The stable `id` used for `aria-describedby` on the input. Points at the\n * assistive-text region (covers both helper text and error text per WAI-ARIA).\n */\n assistiveId: string;\n /**\n * Registers the real form field handle (the return value of `useField()`) so that\n * `triggerValidation()` can call its `reportValidity()`. See `TextFieldHandle.attachFormField`\n * for why this is a post-hoc call instead of a constructor option — the same forward\n * reference applies here for choice fields.\n */\n attachFormField: (formField: { reportValidity(): void }) => void;\n disabled: Readable<boolean>;\n /** Stable `id` for the inline error message element (`aria-errormessage`). */\n errorId: string;\n /** Reactive error text. Empty string when no error is set. */\n errorText: Readable<string>;\n /** Reactive helper text. Empty string when no helper is set. */\n helperText: Readable<string>;\n triggerValidation: (on: Extract<ValidationTrigger, 'blur' | 'change'>) => void;\n};\n\nexport type AssistiveStateOptions = {\n disabled?: Readable<boolean | undefined>;\n error?: Readable<string | undefined>;\n helper?: Readable<string | undefined>;\n validateOn?: Readable<ControlValidationMode>;\n};\n\nexport const createAssistiveState = (options: AssistiveStateOptions): AssistiveStateHandle => {\n const disabled = computed(() => Boolean(options.disabled?.value));\n const assistiveId = createStableId('helper');\n const errorId = createStableId('error');\n const resolvedAssistive = createErrorHelperState({ error: options.error, helper: options.helper });\n\n const ariaDescribedBy = computed(() =>\n resolvedAssistive.value.errorText || resolvedAssistive.value.helperText ? assistiveId : null,\n );\n const ariaErrorMessage = computed(() => (resolvedAssistive.value.errorText ? errorId : null));\n const ariaInvalid = computed<'true' | null>(() => (resolvedAssistive.value.errorText ? 'true' : null));\n\n let formField: { reportValidity(): void } | null = null;\n const attachFormField = (nextFormField: { reportValidity(): void }): void => {\n formField = nextFormField;\n };\n\n const triggerValidation = (on: Extract<ValidationTrigger, 'blur' | 'change'>): void => {\n if (options.validateOn?.value !== on) return;\n\n if (!formField) {\n // Not user-facing — this only fires when a component author wired `validateOn` but\n // forgot the matching `attachFormField(useField(...))` call, so validation silently\n // never runs instead of erroring where the mistake actually is.\n warn(\n \"triggerValidation() called before attachFormField() — validation will not run. See createTextField()/createChoiceField()/createCheckable()'s attachFormField doc comment.\",\n );\n\n return;\n }\n\n formField.reportValidity();\n };\n\n return {\n ariaDescribedBy,\n ariaErrorMessage,\n ariaInvalid,\n assistiveId,\n attachFormField,\n disabled,\n errorId,\n errorText: computed(() => resolvedAssistive.value.errorText),\n helperText: computed(() => resolvedAssistive.value.helperText),\n triggerValidation,\n };\n};\n\n// ── Label state (visible `<label>` + `aria-labelledby`) ────────────────────────\n\nexport type LabelStateHandle = {\n /** `aria-labelledby` value. Non-null when a label is visible. */\n ariaLabelledBy: Readable<string | null>;\n /** Stable `id` for the underlying form control — the `<label for=\"...\">` target. */\n fieldId: string;\n /** Stable `id` for the label element. Stamp this on your `<label id=\"...\">`. */\n labelId: string;\n /** Whether the label should be visible. Use to toggle `hidden` on the `<label>`. */\n labelVisible: Readable<boolean>;\n};\n\nexport type LabelStateOptions = {\n /**\n * Override for label visibility that takes precedence over deriving it from\n * the `label` text signal. Pass a computed signal that checks both prop and\n * slot presence to enable slot-first composition.\n *\n * @example\n * ```ts\n * const hasLabel = computed(() => !!props.label.value || slots.has('label').value);\n * const label = createLabelState({ ...options, hasLabel });\n * ```\n */\n hasLabel?: Readable<boolean>;\n /**\n * When provided, used directly as `fieldId` instead of generating one via\n * `createStableId`. Useful in tests for deterministic ID assertions.\n */\n id?: string;\n /**\n * Label text signal. When provided, `labelVisible` and `ariaLabelledBy` are\n * computed reactively from this value (see `hasLabel` for the slot-first override).\n */\n label?: Readable<string | undefined>;\n /**\n * Label placement signal. Defaults to `'inset'`.\n *\n * Not consumed by `createLabelState()` itself — visibility is driven solely by\n * `hasLabel`/`label`. Threaded through so component authors can apply\n * placement-specific styling (`label-placement` attribute) without a second\n * option; if placement-dependent visibility is ever needed, wire it into\n * `labelVisible` here instead of adding a new option.\n */\n labelPlacement?: Readable<LabelPlacement>;\n prefix?: string;\n};\n\nexport const createLabelState = (options: LabelStateOptions): LabelStateHandle => {\n const fieldId = options.id ?? createStableId(options.prefix ?? 'field');\n const labelId = createStableId('label');\n const label$ = options.label ?? computed(() => undefined);\n\n // Slot-first composition: if the caller provides `hasLabel`, use it instead\n // of deriving visibility purely from the label text signal. This allows\n // components with a `<slot name=\"label\">` to stay visible even when the\n // `label` prop is empty but a slotted label element is present.\n const labelVisible = options.hasLabel ?? computed(() => Boolean(label$.value));\n const ariaLabelledBy = computed(() => (labelVisible.value ? labelId : null));\n\n return { ariaLabelledBy, fieldId, labelId, labelVisible };\n};\n\n// ── Field handle (assistive + label, composed) ──────────────────────────────\n\n/**\n * Common handle returned by `createField` — the base for both `TextFieldHandle`\n * and `ChoiceFieldHandle`. Composes `createAssistiveState` + `createLabelState`;\n * call those directly instead if a field has no visible `<label>` (see\n * `createAssistiveState`'s doc comment).\n *\n * ARIA signals and label state are flat on the handle — no nested `aria` or\n * `label` sub-objects. `labelId`/`labelVisible`/`ariaLabelledBy` are always\n * present but only meaningful when `label`/`hasLabel` was passed — omit both\n * and they resolve to an always-hidden, unreferenced label.\n *\n * @example\n * ```html\n * <label id=\"${labelId}\" ?hidden=\"${() => !labelVisible.value}\">...</label>\n * <input\n * :aria-labelledby=\"${ariaLabelledBy}\"\n * :aria-describedby=\"${ariaDescribedBy}\"\n * :aria-invalid=\"${ariaInvalid}\" />\n * ```\n */\nexport type FieldHandle = AssistiveStateHandle & LabelStateHandle;\n\n/** Options shared by both `createTextField` and `createChoiceField`. */\nexport type FieldOptions = AssistiveStateOptions & LabelStateOptions;\n\nexport const createField = (options: FieldOptions): FieldHandle => ({\n ...createAssistiveState(options),\n ...createLabelState(options),\n});\n"],"mappings":"4FAsBA,IAAa,EAA0B,IAAA,EAAA,EAAA,SAAA,MACrB,CACd,UAAW,EAAQ,OAAO,OAAS,GACnC,WAAY,EAAQ,QAAQ,OAAS,EACvC,EAAE,EAyBS,EAAsB,IAAA,EAAA,EAAA,SAAA,KACJ,CAC3B,IAAM,EAAQ,EAAQ,OAAO,OAAS,GAChC,EAAY,EAAQ,WAAW,MAC/B,EAAkB,OAAO,CAAS,EAClC,EAAiB,OAAO,SAAS,CAAe,GAAK,EAAkB,EAAI,EAAkB,KAC7F,EAAa,IAAmB,KAChC,EAAc,EAAa,GAAG,EAAM,OAAO,KAAK,IAAmB,GACnE,EAAQ,EAAa,EAAM,OAAS,EAAiB,EAE3D,MAAO,CACL,eAAgB,EAAa,GAAS,EAAI,GAC1C,iBAAkB,EAAa,GAAS,IAAO,EAAQ,EAAI,GAC3D,aACF,CACF,CAAC,EAYU,GAAoB,EAAmC,EAAO,YACpE,EAED,EAAQ,eAAuB,GAAG,EAAK,WAEvC,EAAQ,iBAAyB,GAAG,EAAK,aAEtC,EANc,EA4DV,EAAwB,GAAyD,CAC5F,IAAM,GAAA,EAAA,EAAA,SAAA,KAA0B,EAAQ,EAAQ,UAAU,KAAM,EAC1D,GAAA,EAAA,EAAA,eAAA,CAA6B,QAAQ,EACrC,GAAA,EAAA,EAAA,eAAA,CAAyB,OAAO,EAChC,EAAoB,EAAuB,CAAE,MAAO,EAAQ,MAAO,OAAQ,EAAQ,MAAO,CAAC,EAE3F,GAAA,EAAA,EAAA,SAAA,KACJ,EAAkB,MAAM,WAAa,EAAkB,MAAM,WAAa,EAAc,IAC1F,EACM,GAAA,EAAA,EAAA,SAAA,KAAmC,EAAkB,MAAM,UAAY,EAAU,IAAK,EACtF,GAAA,EAAA,EAAA,SAAA,KAA6C,EAAkB,MAAM,UAAY,OAAS,IAAK,EAEjG,EAA+C,KAsBnD,MAAO,CACL,kBACA,mBACA,cACA,cACA,gBA1BuB,GAAoD,CAC3E,EAAY,CACd,EAyBE,WACA,UACA,WAAA,EAAA,EAAA,SAAA,KAA0B,EAAkB,MAAM,SAAS,EAC3D,YAAA,EAAA,EAAA,SAAA,KAA2B,EAAkB,MAAM,UAAU,EAC7D,kBA3ByB,GAA4D,CACjF,KAAQ,YAAY,QAAU,EAElC,IAAI,CAAC,EAAW,CAId,EAAA,KACE,2KACF,EAEA,MACF,CAEA,EAAU,eAAe,CAFzB,CAGF,CAaA,CACF,EAmDa,EAAoB,GAAiD,CAChF,IAAM,EAAU,EAAQ,KAAA,EAAA,EAAA,eAAA,CAAqB,EAAQ,QAAU,OAAO,EAChE,GAAA,EAAA,EAAA,eAAA,CAAyB,OAAO,EAChC,EAAS,EAAQ,QAAA,EAAA,EAAA,SAAA,KAAwB,IAAA,EAAS,EAMlD,EAAe,EAAQ,WAAA,EAAA,EAAA,SAAA,KAA2B,EAAQ,EAAO,KAAM,EAG7E,MAAO,CAAE,gBAAA,EAAA,EAAA,SAAA,KAF8B,EAAa,MAAQ,EAAU,IAE7D,EAAgB,UAAS,UAAS,cAAa,CAC1D,EA6Ba,EAAe,IAAwC,CAClE,GAAG,EAAqB,CAAO,EAC/B,GAAG,EAAiB,CAAO,CAC7B"}
|
|
@@ -1,53 +1,6 @@
|
|
|
1
1
|
import { type Readable } from '@vielzeug/ripple';
|
|
2
2
|
export type ValidationTrigger = 'blur' | 'change' | 'input' | 'submit';
|
|
3
3
|
export type ControlValidationMode = ValidationTrigger | undefined;
|
|
4
|
-
/** Options shared by both `createTextField` and `createChoiceField`. */
|
|
5
|
-
export type FieldOptions = {
|
|
6
|
-
disabled?: Readable<boolean | undefined>;
|
|
7
|
-
error?: Readable<string | undefined>;
|
|
8
|
-
/**
|
|
9
|
-
* Returns the underlying form field object used for validation triggering.
|
|
10
|
-
* Called lazily — safe to return `null` before the first render.
|
|
11
|
-
*/
|
|
12
|
-
getFormField?: () => {
|
|
13
|
-
reportValidity(): void;
|
|
14
|
-
} | null;
|
|
15
|
-
/**
|
|
16
|
-
* Override for label visibility that takes precedence over deriving it from
|
|
17
|
-
* the `label` text signal. Pass a computed signal that checks both prop and
|
|
18
|
-
* slot presence to enable slot-first composition.
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```ts
|
|
22
|
-
* const hasLabel = computed(() => !!props.label.value || slots.has('label').value);
|
|
23
|
-
* const field = createField({ ...options, hasLabel });
|
|
24
|
-
* ```
|
|
25
|
-
*/
|
|
26
|
-
hasLabel?: Readable<boolean>;
|
|
27
|
-
helper?: Readable<string | undefined>;
|
|
28
|
-
/**
|
|
29
|
-
* When provided, used directly as `fieldId` instead of generating one via
|
|
30
|
-
* `createStableId`. Useful in tests for deterministic ID assertions.
|
|
31
|
-
*/
|
|
32
|
-
id?: string;
|
|
33
|
-
/**
|
|
34
|
-
* Label text signal. When provided, `labelVisible` and `ariaLabelledBy` are
|
|
35
|
-
* computed reactively from this value (see `hasLabel` for the slot-first override).
|
|
36
|
-
*/
|
|
37
|
-
label?: Readable<string | undefined>;
|
|
38
|
-
/**
|
|
39
|
-
* Label placement signal. Defaults to `'inset'`.
|
|
40
|
-
*
|
|
41
|
-
* Not currently consumed by `createField()` itself — visibility is driven
|
|
42
|
-
* solely by `hasLabel`/`label`. Threaded through so component authors can
|
|
43
|
-
* apply placement-specific styling (`label-placement` attribute) without a
|
|
44
|
-
* second prop; if placement-dependent visibility is ever needed, wire it
|
|
45
|
-
* into `labelVisible$` in `createField()` instead of adding a new option.
|
|
46
|
-
*/
|
|
47
|
-
labelPlacement?: Readable<LabelPlacement>;
|
|
48
|
-
prefix?: string;
|
|
49
|
-
validateOn?: Readable<ControlValidationMode>;
|
|
50
|
-
};
|
|
51
4
|
export type ErrorHelperState = {
|
|
52
5
|
errorText: string;
|
|
53
6
|
helperText: string;
|
|
@@ -77,72 +30,132 @@ export type CounterOptions = {
|
|
|
77
30
|
* ```
|
|
78
31
|
*/
|
|
79
32
|
export declare const createCounterState: (options: CounterOptions) => Readable<CounterState>;
|
|
80
|
-
export type LabelPlacement = 'inset' | 'outside' | undefined;
|
|
81
33
|
/**
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* ARIA signals and label state are flat on the handle — no nested `aria` or
|
|
86
|
-
* `label` sub-objects.
|
|
34
|
+
* The single implementation of "which modifier class does this counter state map to" —
|
|
35
|
+
* every field with a character counter (`ore-input`, `ore-textarea`, `ore-message-composer`)
|
|
36
|
+
* renders the identical near-limit/at-limit styling, so this is the one place that decides it.
|
|
87
37
|
*
|
|
88
38
|
* @example
|
|
89
|
-
* ```
|
|
90
|
-
*
|
|
91
|
-
* <input
|
|
92
|
-
* :aria-labelledby="${ariaLabelledBy}"
|
|
93
|
-
* :aria-describedby="${ariaDescribedBy}"
|
|
94
|
-
* :aria-invalid="${ariaInvalid}" />
|
|
39
|
+
* ```ts
|
|
40
|
+
* html`<span class="${() => counterClassName(counter?.value)}">...</span>`
|
|
95
41
|
* ```
|
|
96
42
|
*/
|
|
97
|
-
|
|
98
|
-
export type
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
43
|
+
export declare const counterClassName: (counter: CounterState | undefined, base?: string) => string;
|
|
44
|
+
export type LabelPlacement = 'inset' | 'outside' | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* A field's error/helper/disabled/validation-trigger state — everything an input
|
|
47
|
+
* needs *except* a visible `<label>`. Split out from label state (below) because
|
|
48
|
+
* not every field has one: `ore-message-composer` names itself via `aria-label`
|
|
49
|
+
* (a chat composer's accessible name conventionally comes from context/placeholder,
|
|
50
|
+
* not a floating `<label>`), so it composes this directly instead of pulling in
|
|
51
|
+
* label plumbing it can't use. `createField()` composes both for fields that do
|
|
52
|
+
* render a `<label>`.
|
|
53
|
+
*/
|
|
54
|
+
export type AssistiveStateHandle = {
|
|
105
55
|
/** `aria-describedby` value. Non-null when helper or error text is present. */
|
|
106
56
|
ariaDescribedBy: Readable<string | null>;
|
|
107
57
|
/** `aria-errormessage` value. Non-null when error text is present. */
|
|
108
58
|
ariaErrorMessage: Readable<string | null>;
|
|
109
59
|
/** `aria-invalid` value. `'true'` when error text is present, otherwise `null`. */
|
|
110
60
|
ariaInvalid: Readable<'true' | null>;
|
|
111
|
-
/** `aria-labelledby` value. Non-null when a label is visible. */
|
|
112
|
-
ariaLabelledBy: Readable<string | null>;
|
|
113
|
-
/**
|
|
114
|
-
* Returns a snapshot of the four ARIA attributes as a plain object.
|
|
115
|
-
* Spread into a template bind call to avoid repeating each signal individually.
|
|
116
|
-
*
|
|
117
|
-
* @example
|
|
118
|
-
* ```ts
|
|
119
|
-
* bind(inputEl, { attr: field.ariaProps() });
|
|
120
|
-
* ```
|
|
121
|
-
*/
|
|
122
|
-
ariaProps(): AriaProps;
|
|
123
61
|
/**
|
|
124
62
|
* The stable `id` used for `aria-describedby` on the input. Points at the
|
|
125
63
|
* assistive-text region (covers both helper text and error text per WAI-ARIA).
|
|
126
64
|
*/
|
|
127
65
|
assistiveId: string;
|
|
66
|
+
/**
|
|
67
|
+
* Registers the real form field handle (the return value of `useField()`) so that
|
|
68
|
+
* `triggerValidation()` can call its `reportValidity()`. See `TextFieldHandle.attachFormField`
|
|
69
|
+
* for why this is a post-hoc call instead of a constructor option — the same forward
|
|
70
|
+
* reference applies here for choice fields.
|
|
71
|
+
*/
|
|
72
|
+
attachFormField: (formField: {
|
|
73
|
+
reportValidity(): void;
|
|
74
|
+
}) => void;
|
|
128
75
|
disabled: Readable<boolean>;
|
|
129
76
|
/** Stable `id` for the inline error message element (`aria-errormessage`). */
|
|
130
77
|
errorId: string;
|
|
131
78
|
/** Reactive error text. Empty string when no error is set. */
|
|
132
79
|
errorText: Readable<string>;
|
|
133
|
-
fieldId: string;
|
|
134
80
|
/** Reactive helper text. Empty string when no helper is set. */
|
|
135
81
|
helperText: Readable<string>;
|
|
82
|
+
triggerValidation: (on: Extract<ValidationTrigger, 'blur' | 'change'>) => void;
|
|
83
|
+
};
|
|
84
|
+
export type AssistiveStateOptions = {
|
|
85
|
+
disabled?: Readable<boolean | undefined>;
|
|
86
|
+
error?: Readable<string | undefined>;
|
|
87
|
+
helper?: Readable<string | undefined>;
|
|
88
|
+
validateOn?: Readable<ControlValidationMode>;
|
|
89
|
+
};
|
|
90
|
+
export declare const createAssistiveState: (options: AssistiveStateOptions) => AssistiveStateHandle;
|
|
91
|
+
export type LabelStateHandle = {
|
|
92
|
+
/** `aria-labelledby` value. Non-null when a label is visible. */
|
|
93
|
+
ariaLabelledBy: Readable<string | null>;
|
|
94
|
+
/** Stable `id` for the underlying form control — the `<label for="...">` target. */
|
|
95
|
+
fieldId: string;
|
|
136
96
|
/** Stable `id` for the label element. Stamp this on your `<label id="...">`. */
|
|
137
97
|
labelId: string;
|
|
138
98
|
/** Whether the label should be visible. Use to toggle `hidden` on the `<label>`. */
|
|
139
99
|
labelVisible: Readable<boolean>;
|
|
140
|
-
triggerValidation: (on: Extract<ValidationTrigger, 'blur' | 'change'>) => void;
|
|
141
100
|
};
|
|
101
|
+
export type LabelStateOptions = {
|
|
102
|
+
/**
|
|
103
|
+
* Override for label visibility that takes precedence over deriving it from
|
|
104
|
+
* the `label` text signal. Pass a computed signal that checks both prop and
|
|
105
|
+
* slot presence to enable slot-first composition.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```ts
|
|
109
|
+
* const hasLabel = computed(() => !!props.label.value || slots.has('label').value);
|
|
110
|
+
* const label = createLabelState({ ...options, hasLabel });
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
hasLabel?: Readable<boolean>;
|
|
114
|
+
/**
|
|
115
|
+
* When provided, used directly as `fieldId` instead of generating one via
|
|
116
|
+
* `createStableId`. Useful in tests for deterministic ID assertions.
|
|
117
|
+
*/
|
|
118
|
+
id?: string;
|
|
119
|
+
/**
|
|
120
|
+
* Label text signal. When provided, `labelVisible` and `ariaLabelledBy` are
|
|
121
|
+
* computed reactively from this value (see `hasLabel` for the slot-first override).
|
|
122
|
+
*/
|
|
123
|
+
label?: Readable<string | undefined>;
|
|
124
|
+
/**
|
|
125
|
+
* Label placement signal. Defaults to `'inset'`.
|
|
126
|
+
*
|
|
127
|
+
* Not consumed by `createLabelState()` itself — visibility is driven solely by
|
|
128
|
+
* `hasLabel`/`label`. Threaded through so component authors can apply
|
|
129
|
+
* placement-specific styling (`label-placement` attribute) without a second
|
|
130
|
+
* option; if placement-dependent visibility is ever needed, wire it into
|
|
131
|
+
* `labelVisible` here instead of adding a new option.
|
|
132
|
+
*/
|
|
133
|
+
labelPlacement?: Readable<LabelPlacement>;
|
|
134
|
+
prefix?: string;
|
|
135
|
+
};
|
|
136
|
+
export declare const createLabelState: (options: LabelStateOptions) => LabelStateHandle;
|
|
142
137
|
/**
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
138
|
+
* Common handle returned by `createField` — the base for both `TextFieldHandle`
|
|
139
|
+
* and `ChoiceFieldHandle`. Composes `createAssistiveState` + `createLabelState`;
|
|
140
|
+
* call those directly instead if a field has no visible `<label>` (see
|
|
141
|
+
* `createAssistiveState`'s doc comment).
|
|
142
|
+
*
|
|
143
|
+
* ARIA signals and label state are flat on the handle — no nested `aria` or
|
|
144
|
+
* `label` sub-objects. `labelId`/`labelVisible`/`ariaLabelledBy` are always
|
|
145
|
+
* present but only meaningful when `label`/`hasLabel` was passed — omit both
|
|
146
|
+
* and they resolve to an always-hidden, unreferenced label.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```html
|
|
150
|
+
* <label id="${labelId}" ?hidden="${() => !labelVisible.value}">...</label>
|
|
151
|
+
* <input
|
|
152
|
+
* :aria-labelledby="${ariaLabelledBy}"
|
|
153
|
+
* :aria-describedby="${ariaDescribedBy}"
|
|
154
|
+
* :aria-invalid="${ariaInvalid}" />
|
|
155
|
+
* ```
|
|
146
156
|
*/
|
|
157
|
+
export type FieldHandle = AssistiveStateHandle & LabelStateHandle;
|
|
158
|
+
/** Options shared by both `createTextField` and `createChoiceField`. */
|
|
159
|
+
export type FieldOptions = AssistiveStateOptions & LabelStateOptions;
|
|
147
160
|
export declare const createField: (options: FieldOptions) => FieldHandle;
|
|
148
161
|
//# sourceMappingURL=field-base.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-base.d.ts","sourceRoot":"","sources":["../../src/headless/field-base.ts"],"names":[],"mappings":"AACA,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"field-base.d.ts","sourceRoot":"","sources":["../../src/headless/field-base.ts"],"names":[],"mappings":"AACA,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAM3D,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;AACvE,MAAM,MAAM,qBAAqB,GAAG,iBAAiB,GAAG,SAAS,CAAC;AAIlE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACrC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CACvC,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,SAAS,kBAAkB,KAAG,QAAQ,CAAC,gBAAgB,CAIzF,CAAC;AAIN,sDAAsD;AACtD,MAAM,MAAM,YAAY,GAAG;IACzB,cAAc,EAAE,OAAO,CAAC;IACxB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,KAAK,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CACrC,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,cAAc,KAAG,QAAQ,CAAC,YAAY,CAe9E,CAAC;AAEL;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,GAAI,SAAS,YAAY,GAAG,SAAS,EAAE,aAAgB,KAAG,MAQtF,CAAC;AAIF,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;AAI7D;;;;;;;;GAQG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,+EAA+E;IAC/E,eAAe,EAAE,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACzC,sEAAsE;IACtE,gBAAgB,EAAE,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1C,mFAAmF;IACnF,WAAW,EAAE,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACrC;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,eAAe,EAAE,CAAC,SAAS,EAAE;QAAE,cAAc,IAAI,IAAI,CAAA;KAAE,KAAK,IAAI,CAAC;IACjE,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC5B,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC5B,gEAAgE;IAChE,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC7B,iBAAiB,EAAE,CAAC,EAAE,EAAE,OAAO,CAAC,iBAAiB,EAAE,MAAM,GAAG,QAAQ,CAAC,KAAK,IAAI,CAAC;CAChF,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACzC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACrC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACtC,UAAU,CAAC,EAAE,QAAQ,CAAC,qBAAqB,CAAC,CAAC;CAC9C,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAI,SAAS,qBAAqB,KAAG,oBA8CrE,CAAC;AAIF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,iEAAiE;IACjE,cAAc,EAAE,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxC,oFAAoF;IACpF,OAAO,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,OAAO,EAAE,MAAM,CAAC;IAChB,oFAAoF;IACpF,YAAY,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC7B;;;OAGG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACrC;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,SAAS,iBAAiB,KAAG,gBAa7D,CAAC;AAIF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,WAAW,GAAG,oBAAoB,GAAG,gBAAgB,CAAC;AAElE,wEAAwE;AACxE,MAAM,MAAM,YAAY,GAAG,qBAAqB,GAAG,iBAAiB,CAAC;AAErE,eAAO,MAAM,WAAW,GAAI,SAAS,YAAY,KAAG,WAGlD,CAAC"}
|
|
@@ -1,46 +1,57 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { warn as e } from "../_dev.js";
|
|
2
|
+
import { createStableId as t } from "@vielzeug/ore";
|
|
3
|
+
import { computed as n } from "@vielzeug/ripple";
|
|
3
4
|
//#region src/headless/field-base.ts
|
|
4
|
-
var
|
|
5
|
+
var r = (e) => n(() => ({
|
|
5
6
|
errorText: e.error?.value ?? "",
|
|
6
7
|
helperText: e.helper?.value ?? ""
|
|
7
|
-
})),
|
|
8
|
+
})), i = (e) => n(() => {
|
|
8
9
|
let t = e.value?.value ?? "", n = e.maxLength?.value, r = Number(n), i = Number.isFinite(r) && r > 0 ? r : null, a = i !== null, o = a ? `${t.length} / ${i}` : "", s = a ? t.length / i : 0;
|
|
9
10
|
return {
|
|
10
11
|
counterAtLimit: a ? s >= 1 : !1,
|
|
11
12
|
counterNearLimit: a ? s >= .9 && s < 1 : !1,
|
|
12
13
|
counterText: o
|
|
13
14
|
};
|
|
14
|
-
}),
|
|
15
|
-
let
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
helper: r.helper
|
|
20
|
-
}), m = t(() => p.value.errorText || p.value.helperText ? s : null), h = t(() => p.value.errorText ? u : null), g = t(() => p.value.errorText ? "true" : null), _ = t(() => f.value ? l : null);
|
|
15
|
+
}), a = (e, t = "counter") => e ? e.counterAtLimit ? `${t} at-limit` : e.counterNearLimit ? `${t} near-limit` : t : t, o = (i) => {
|
|
16
|
+
let a = n(() => !!i.disabled?.value), o = t("helper"), s = t("error"), c = r({
|
|
17
|
+
error: i.error,
|
|
18
|
+
helper: i.helper
|
|
19
|
+
}), l = n(() => c.value.errorText || c.value.helperText ? o : null), u = n(() => c.value.errorText ? s : null), d = n(() => c.value.errorText ? "true" : null), f = null;
|
|
21
20
|
return {
|
|
22
|
-
ariaDescribedBy:
|
|
23
|
-
ariaErrorMessage:
|
|
24
|
-
ariaInvalid:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
21
|
+
ariaDescribedBy: l,
|
|
22
|
+
ariaErrorMessage: u,
|
|
23
|
+
ariaInvalid: d,
|
|
24
|
+
assistiveId: o,
|
|
25
|
+
attachFormField: (e) => {
|
|
26
|
+
f = e;
|
|
27
|
+
},
|
|
28
|
+
disabled: a,
|
|
29
|
+
errorId: s,
|
|
30
|
+
errorText: n(() => c.value.errorText),
|
|
31
|
+
helperText: n(() => c.value.helperText),
|
|
32
|
+
triggerValidation: (t) => {
|
|
33
|
+
if (i.validateOn?.value === t) {
|
|
34
|
+
if (!f) {
|
|
35
|
+
e("triggerValidation() called before attachFormField() — validation will not run. See createTextField()/createChoiceField()/createCheckable()'s attachFormField doc comment.");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
f.reportValidity();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
41
|
};
|
|
42
|
-
}
|
|
42
|
+
}, s = (e) => {
|
|
43
|
+
let r = e.id ?? t(e.prefix ?? "field"), i = t("label"), a = e.label ?? n(() => void 0), o = e.hasLabel ?? n(() => !!a.value);
|
|
44
|
+
return {
|
|
45
|
+
ariaLabelledBy: n(() => o.value ? i : null),
|
|
46
|
+
fieldId: r,
|
|
47
|
+
labelId: i,
|
|
48
|
+
labelVisible: o
|
|
49
|
+
};
|
|
50
|
+
}, c = (e) => ({
|
|
51
|
+
...o(e),
|
|
52
|
+
...s(e)
|
|
53
|
+
});
|
|
43
54
|
//#endregion
|
|
44
|
-
export {
|
|
55
|
+
export { a as counterClassName, o as createAssistiveState, i as createCounterState, r as createErrorHelperState, c as createField, s as createLabelState };
|
|
45
56
|
|
|
46
57
|
//# sourceMappingURL=field-base.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-base.js","names":[],"sources":["../../src/headless/field-base.ts"],"sourcesContent":["import { createStableId } from '@vielzeug/ore';\nimport { computed, type Readable } from '@vielzeug/ripple';\n\n// ── Validation / context types ────────────────────────────────────────────────\n\nexport type ValidationTrigger = 'blur' | 'change' | 'input' | 'submit';\nexport type ControlValidationMode = ValidationTrigger | undefined;\n\n// ── Field options + handle ───────────────────────────────────────────────────\n\n/** Options shared by both `createTextField` and `createChoiceField`. */\nexport type FieldOptions = {\n disabled?: Readable<boolean | undefined>;\n error?: Readable<string | undefined>;\n /**\n * Returns the underlying form field object used for validation triggering.\n * Called lazily — safe to return `null` before the first render.\n */\n getFormField?: () => { reportValidity(): void } | null;\n /**\n * Override for label visibility that takes precedence over deriving it from\n * the `label` text signal. Pass a computed signal that checks both prop and\n * slot presence to enable slot-first composition.\n *\n * @example\n * ```ts\n * const hasLabel = computed(() => !!props.label.value || slots.has('label').value);\n * const field = createField({ ...options, hasLabel });\n * ```\n */\n hasLabel?: Readable<boolean>;\n helper?: Readable<string | undefined>;\n /**\n * When provided, used directly as `fieldId` instead of generating one via\n * `createStableId`. Useful in tests for deterministic ID assertions.\n */\n id?: string;\n /**\n * Label text signal. When provided, `labelVisible` and `ariaLabelledBy` are\n * computed reactively from this value (see `hasLabel` for the slot-first override).\n */\n label?: Readable<string | undefined>;\n /**\n * Label placement signal. Defaults to `'inset'`.\n *\n * Not currently consumed by `createField()` itself — visibility is driven\n * solely by `hasLabel`/`label`. Threaded through so component authors can\n * apply placement-specific styling (`label-placement` attribute) without a\n * second prop; if placement-dependent visibility is ever needed, wire it\n * into `labelVisible$` in `createField()` instead of adding a new option.\n */\n labelPlacement?: Readable<LabelPlacement>;\n prefix?: string;\n validateOn?: Readable<ControlValidationMode>;\n};\n\n// ── Error/helper assistive state ──────────────────────────────────────────────\n\nexport type ErrorHelperState = {\n errorText: string;\n helperText: string;\n};\n\nexport type ErrorHelperOptions = {\n error?: Readable<string | undefined>;\n helper?: Readable<string | undefined>;\n};\n\nexport const createErrorHelperState = (options: ErrorHelperOptions): Readable<ErrorHelperState> =>\n computed(() => ({\n errorText: options.error?.value ?? '',\n helperText: options.helper?.value ?? '',\n }));\n\n// ── Counter state (opt-in) ─────────────────────────────────────────────────────\n\n/** Counter state for text fields with `maxLength`. */\nexport type CounterState = {\n counterAtLimit: boolean;\n counterNearLimit: boolean;\n counterText: string;\n};\n\nexport type CounterOptions = {\n maxLength?: Readable<number | undefined>;\n value: Readable<string | undefined>;\n};\n\n/**\n * Creates a reactive counter state signal for text fields with `maxLength`.\n * Only call this when `maxLength` may be set — the state is genuinely opt-in.\n *\n * @example\n * ```ts\n * const counter = createCounterState({ value: tf.value, maxLength: props.maxlength });\n * ```\n */\nexport const createCounterState = (options: CounterOptions): Readable<CounterState> =>\n computed<CounterState>(() => {\n const value = options.value?.value ?? '';\n const maxLength = options.maxLength?.value;\n const parsedMaxLength = Number(maxLength);\n const validMaxLength = Number.isFinite(parsedMaxLength) && parsedMaxLength > 0 ? parsedMaxLength : null;\n const hasCounter = validMaxLength !== null;\n const counterText = hasCounter ? `${value.length} / ${validMaxLength}` : '';\n const ratio = hasCounter ? value.length / validMaxLength : 0;\n\n return {\n counterAtLimit: hasCounter ? ratio >= 1 : false,\n counterNearLimit: hasCounter ? ratio >= 0.9 && ratio < 1 : false,\n counterText,\n };\n });\n\n// ── Label placement ───────────────────────────────────────────────────────────\n\nexport type LabelPlacement = 'inset' | 'outside' | undefined;\n\n// ── Field handle ──────────────────────────────────────────────────────────────\n\n/**\n * Common handle returned by `createField` — the base for both `TextFieldHandle`\n * and `ChoiceFieldHandle`.\n *\n * ARIA signals and label state are flat on the handle — no nested `aria` or\n * `label` sub-objects.\n *\n * @example\n * ```html\n * <label id=\"${labelId}\" ?hidden=\"${() => !labelVisible.value}\">...</label>\n * <input\n * :aria-labelledby=\"${ariaLabelledBy}\"\n * :aria-describedby=\"${ariaDescribedBy}\"\n * :aria-invalid=\"${ariaInvalid}\" />\n * ```\n */\n/** Flat ARIA attribute bag ready to spread into a template bind call. */\nexport type AriaProps = {\n 'aria-describedby': string | null;\n 'aria-errormessage': string | null;\n 'aria-invalid': 'true' | null;\n 'aria-labelledby': string | null;\n};\n\nexport type FieldHandle = {\n /** `aria-describedby` value. Non-null when helper or error text is present. */\n ariaDescribedBy: Readable<string | null>;\n /** `aria-errormessage` value. Non-null when error text is present. */\n ariaErrorMessage: Readable<string | null>;\n /** `aria-invalid` value. `'true'` when error text is present, otherwise `null`. */\n ariaInvalid: Readable<'true' | null>;\n /** `aria-labelledby` value. Non-null when a label is visible. */\n ariaLabelledBy: Readable<string | null>;\n /**\n * Returns a snapshot of the four ARIA attributes as a plain object.\n * Spread into a template bind call to avoid repeating each signal individually.\n *\n * @example\n * ```ts\n * bind(inputEl, { attr: field.ariaProps() });\n * ```\n */\n ariaProps(): AriaProps;\n /**\n * The stable `id` used for `aria-describedby` on the input. Points at the\n * assistive-text region (covers both helper text and error text per WAI-ARIA).\n */\n assistiveId: string;\n disabled: Readable<boolean>;\n /** Stable `id` for the inline error message element (`aria-errormessage`). */\n errorId: string;\n /** Reactive error text. Empty string when no error is set. */\n errorText: Readable<string>;\n fieldId: string;\n /** Reactive helper text. Empty string when no helper is set. */\n helperText: Readable<string>;\n /** Stable `id` for the label element. Stamp this on your `<label id=\"...\">`. */\n labelId: string;\n /** Whether the label should be visible. Use to toggle `hidden` on the `<label>`. */\n labelVisible: Readable<boolean>;\n triggerValidation: (on: Extract<ValidationTrigger, 'blur' | 'change'>) => void;\n};\n\n/**\n * Composes the field triad — IDs, assistive state, and label state — into a\n * single `FieldHandle`. Both `createTextField` and `createChoiceField` build\n * on top of this.\n */\nexport const createField = (options: FieldOptions): FieldHandle => {\n // ── Core ──────────────────────────────────────────────────────────────────\n const disabled = computed(() => Boolean(options.disabled?.value));\n const validateOn = options.validateOn;\n const fieldId = options.id ?? createStableId(options.prefix ?? 'field');\n const assistiveId = createStableId('helper');\n\n const triggerValidation = (on: Extract<ValidationTrigger, 'blur' | 'change'>): void => {\n if (validateOn?.value === on) options.getFormField?.()?.reportValidity();\n };\n\n // ── Label + ARIA state ────────────────────────────────────────────────────\n const labelId = createStableId('label');\n const errorId = createStableId('error');\n\n const label$ = options.label ?? computed(() => undefined);\n\n // Slot-first composition: if the caller provides `hasLabel`, use it instead\n // of deriving visibility purely from the label text signal. This allows\n // components with a `<slot name=\"label\">` to stay visible even when the\n // `label` prop is empty but a slotted label element is present.\n const labelVisible$ = options.hasLabel ?? computed(() => Boolean(label$.value));\n\n const resolvedAssistive = createErrorHelperState({ error: options.error, helper: options.helper });\n\n const ariaDescribedBy = computed(() =>\n resolvedAssistive.value.errorText || resolvedAssistive.value.helperText ? assistiveId : null,\n );\n const ariaErrorMessage = computed(() => (resolvedAssistive.value.errorText ? errorId : null));\n const ariaInvalid = computed<'true' | null>(() => (resolvedAssistive.value.errorText ? 'true' : null));\n const ariaLabelledBy = computed(() => (labelVisible$.value ? labelId : null));\n\n return {\n ariaDescribedBy,\n ariaErrorMessage,\n ariaInvalid,\n ariaLabelledBy,\n ariaProps: () => ({\n 'aria-describedby': ariaDescribedBy.value,\n 'aria-errormessage': ariaErrorMessage.value,\n 'aria-invalid': ariaInvalid.value,\n 'aria-labelledby': ariaLabelledBy.value,\n }),\n assistiveId,\n disabled,\n errorId,\n errorText: computed(() => resolvedAssistive.value.errorText),\n fieldId,\n helperText: computed(() => resolvedAssistive.value.helperText),\n labelId,\n labelVisible: labelVisible$,\n triggerValidation,\n };\n};\n"],"mappings":";;;AAoEA,IAAa,KAA0B,MACrC,SAAgB;CACd,WAAW,EAAQ,OAAO,SAAS;CACnC,YAAY,EAAQ,QAAQ,SAAS;AACvC,EAAE,GAyBS,KAAsB,MACjC,QAA6B;CAC3B,IAAM,IAAQ,EAAQ,OAAO,SAAS,IAChC,IAAY,EAAQ,WAAW,OAC/B,IAAkB,OAAO,CAAS,GAClC,IAAiB,OAAO,SAAS,CAAe,KAAK,IAAkB,IAAI,IAAkB,MAC7F,IAAa,MAAmB,MAChC,IAAc,IAAa,GAAG,EAAM,OAAO,KAAK,MAAmB,IACnE,IAAQ,IAAa,EAAM,SAAS,IAAiB;CAE3D,OAAO;EACL,gBAAgB,IAAa,KAAS,IAAI;EAC1C,kBAAkB,IAAa,KAAS,MAAO,IAAQ,IAAI;EAC3D;CACF;AACF,CAAC,GA4EU,KAAe,MAAuC;CAEjE,IAAM,IAAW,QAAe,EAAQ,EAAQ,UAAU,KAAM,GAC1D,IAAa,EAAQ,YACrB,IAAU,EAAQ,MAAM,EAAe,EAAQ,UAAU,OAAO,GAChE,IAAc,EAAe,QAAQ,GAErC,KAAqB,MAA4D;EACrF,AAAI,GAAY,UAAU,KAAI,EAAQ,eAAe,CAAC,EAAE,eAAe;CACzE,GAGM,IAAU,EAAe,OAAO,GAChC,IAAU,EAAe,OAAO,GAEhC,IAAS,EAAQ,SAAS,QAAe,KAAA,CAAS,GAMlD,IAAgB,EAAQ,YAAY,QAAe,EAAQ,EAAO,KAAM,GAExE,IAAoB,EAAuB;EAAE,OAAO,EAAQ;EAAO,QAAQ,EAAQ;CAAO,CAAC,GAE3F,IAAkB,QACtB,EAAkB,MAAM,aAAa,EAAkB,MAAM,aAAa,IAAc,IAC1F,GACM,IAAmB,QAAgB,EAAkB,MAAM,YAAY,IAAU,IAAK,GACtF,IAAc,QAA+B,EAAkB,MAAM,YAAY,SAAS,IAAK,GAC/F,IAAiB,QAAgB,EAAc,QAAQ,IAAU,IAAK;CAE5E,OAAO;EACL;EACA;EACA;EACA;EACA,kBAAkB;GAChB,oBAAoB,EAAgB;GACpC,qBAAqB,EAAiB;GACtC,gBAAgB,EAAY;GAC5B,mBAAmB,EAAe;EACpC;EACA;EACA;EACA;EACA,WAAW,QAAe,EAAkB,MAAM,SAAS;EAC3D;EACA,YAAY,QAAe,EAAkB,MAAM,UAAU;EAC7D;EACA,cAAc;EACd;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"field-base.js","names":[],"sources":["../../src/headless/field-base.ts"],"sourcesContent":["import { createStableId } from '@vielzeug/ore';\nimport { computed, type Readable } from '@vielzeug/ripple';\n\nimport { warn } from '../_dev';\n\n// ── Validation / context types ────────────────────────────────────────────────\n\nexport type ValidationTrigger = 'blur' | 'change' | 'input' | 'submit';\nexport type ControlValidationMode = ValidationTrigger | undefined;\n\n// ── Error/helper assistive state ──────────────────────────────────────────────\n\nexport type ErrorHelperState = {\n errorText: string;\n helperText: string;\n};\n\nexport type ErrorHelperOptions = {\n error?: Readable<string | undefined>;\n helper?: Readable<string | undefined>;\n};\n\nexport const createErrorHelperState = (options: ErrorHelperOptions): Readable<ErrorHelperState> =>\n computed(() => ({\n errorText: options.error?.value ?? '',\n helperText: options.helper?.value ?? '',\n }));\n\n// ── Counter state (opt-in) ─────────────────────────────────────────────────────\n\n/** Counter state for text fields with `maxLength`. */\nexport type CounterState = {\n counterAtLimit: boolean;\n counterNearLimit: boolean;\n counterText: string;\n};\n\nexport type CounterOptions = {\n maxLength?: Readable<number | undefined>;\n value: Readable<string | undefined>;\n};\n\n/**\n * Creates a reactive counter state signal for text fields with `maxLength`.\n * Only call this when `maxLength` may be set — the state is genuinely opt-in.\n *\n * @example\n * ```ts\n * const counter = createCounterState({ value: tf.value, maxLength: props.maxlength });\n * ```\n */\nexport const createCounterState = (options: CounterOptions): Readable<CounterState> =>\n computed<CounterState>(() => {\n const value = options.value?.value ?? '';\n const maxLength = options.maxLength?.value;\n const parsedMaxLength = Number(maxLength);\n const validMaxLength = Number.isFinite(parsedMaxLength) && parsedMaxLength > 0 ? parsedMaxLength : null;\n const hasCounter = validMaxLength !== null;\n const counterText = hasCounter ? `${value.length} / ${validMaxLength}` : '';\n const ratio = hasCounter ? value.length / validMaxLength : 0;\n\n return {\n counterAtLimit: hasCounter ? ratio >= 1 : false,\n counterNearLimit: hasCounter ? ratio >= 0.9 && ratio < 1 : false,\n counterText,\n };\n });\n\n/**\n * The single implementation of \"which modifier class does this counter state map to\" —\n * every field with a character counter (`ore-input`, `ore-textarea`, `ore-message-composer`)\n * renders the identical near-limit/at-limit styling, so this is the one place that decides it.\n *\n * @example\n * ```ts\n * html`<span class=\"${() => counterClassName(counter?.value)}\">...</span>`\n * ```\n */\nexport const counterClassName = (counter: CounterState | undefined, base = 'counter'): string => {\n if (!counter) return base;\n\n if (counter.counterAtLimit) return `${base} at-limit`;\n\n if (counter.counterNearLimit) return `${base} near-limit`;\n\n return base;\n};\n\n// ── Label placement ───────────────────────────────────────────────────────────\n\nexport type LabelPlacement = 'inset' | 'outside' | undefined;\n\n// ── Assistive state (error/helper text, describedby/errormessage/invalid) ─────\n\n/**\n * A field's error/helper/disabled/validation-trigger state — everything an input\n * needs *except* a visible `<label>`. Split out from label state (below) because\n * not every field has one: `ore-message-composer` names itself via `aria-label`\n * (a chat composer's accessible name conventionally comes from context/placeholder,\n * not a floating `<label>`), so it composes this directly instead of pulling in\n * label plumbing it can't use. `createField()` composes both for fields that do\n * render a `<label>`.\n */\nexport type AssistiveStateHandle = {\n /** `aria-describedby` value. Non-null when helper or error text is present. */\n ariaDescribedBy: Readable<string | null>;\n /** `aria-errormessage` value. Non-null when error text is present. */\n ariaErrorMessage: Readable<string | null>;\n /** `aria-invalid` value. `'true'` when error text is present, otherwise `null`. */\n ariaInvalid: Readable<'true' | null>;\n /**\n * The stable `id` used for `aria-describedby` on the input. Points at the\n * assistive-text region (covers both helper text and error text per WAI-ARIA).\n */\n assistiveId: string;\n /**\n * Registers the real form field handle (the return value of `useField()`) so that\n * `triggerValidation()` can call its `reportValidity()`. See `TextFieldHandle.attachFormField`\n * for why this is a post-hoc call instead of a constructor option — the same forward\n * reference applies here for choice fields.\n */\n attachFormField: (formField: { reportValidity(): void }) => void;\n disabled: Readable<boolean>;\n /** Stable `id` for the inline error message element (`aria-errormessage`). */\n errorId: string;\n /** Reactive error text. Empty string when no error is set. */\n errorText: Readable<string>;\n /** Reactive helper text. Empty string when no helper is set. */\n helperText: Readable<string>;\n triggerValidation: (on: Extract<ValidationTrigger, 'blur' | 'change'>) => void;\n};\n\nexport type AssistiveStateOptions = {\n disabled?: Readable<boolean | undefined>;\n error?: Readable<string | undefined>;\n helper?: Readable<string | undefined>;\n validateOn?: Readable<ControlValidationMode>;\n};\n\nexport const createAssistiveState = (options: AssistiveStateOptions): AssistiveStateHandle => {\n const disabled = computed(() => Boolean(options.disabled?.value));\n const assistiveId = createStableId('helper');\n const errorId = createStableId('error');\n const resolvedAssistive = createErrorHelperState({ error: options.error, helper: options.helper });\n\n const ariaDescribedBy = computed(() =>\n resolvedAssistive.value.errorText || resolvedAssistive.value.helperText ? assistiveId : null,\n );\n const ariaErrorMessage = computed(() => (resolvedAssistive.value.errorText ? errorId : null));\n const ariaInvalid = computed<'true' | null>(() => (resolvedAssistive.value.errorText ? 'true' : null));\n\n let formField: { reportValidity(): void } | null = null;\n const attachFormField = (nextFormField: { reportValidity(): void }): void => {\n formField = nextFormField;\n };\n\n const triggerValidation = (on: Extract<ValidationTrigger, 'blur' | 'change'>): void => {\n if (options.validateOn?.value !== on) return;\n\n if (!formField) {\n // Not user-facing — this only fires when a component author wired `validateOn` but\n // forgot the matching `attachFormField(useField(...))` call, so validation silently\n // never runs instead of erroring where the mistake actually is.\n warn(\n \"triggerValidation() called before attachFormField() — validation will not run. See createTextField()/createChoiceField()/createCheckable()'s attachFormField doc comment.\",\n );\n\n return;\n }\n\n formField.reportValidity();\n };\n\n return {\n ariaDescribedBy,\n ariaErrorMessage,\n ariaInvalid,\n assistiveId,\n attachFormField,\n disabled,\n errorId,\n errorText: computed(() => resolvedAssistive.value.errorText),\n helperText: computed(() => resolvedAssistive.value.helperText),\n triggerValidation,\n };\n};\n\n// ── Label state (visible `<label>` + `aria-labelledby`) ────────────────────────\n\nexport type LabelStateHandle = {\n /** `aria-labelledby` value. Non-null when a label is visible. */\n ariaLabelledBy: Readable<string | null>;\n /** Stable `id` for the underlying form control — the `<label for=\"...\">` target. */\n fieldId: string;\n /** Stable `id` for the label element. Stamp this on your `<label id=\"...\">`. */\n labelId: string;\n /** Whether the label should be visible. Use to toggle `hidden` on the `<label>`. */\n labelVisible: Readable<boolean>;\n};\n\nexport type LabelStateOptions = {\n /**\n * Override for label visibility that takes precedence over deriving it from\n * the `label` text signal. Pass a computed signal that checks both prop and\n * slot presence to enable slot-first composition.\n *\n * @example\n * ```ts\n * const hasLabel = computed(() => !!props.label.value || slots.has('label').value);\n * const label = createLabelState({ ...options, hasLabel });\n * ```\n */\n hasLabel?: Readable<boolean>;\n /**\n * When provided, used directly as `fieldId` instead of generating one via\n * `createStableId`. Useful in tests for deterministic ID assertions.\n */\n id?: string;\n /**\n * Label text signal. When provided, `labelVisible` and `ariaLabelledBy` are\n * computed reactively from this value (see `hasLabel` for the slot-first override).\n */\n label?: Readable<string | undefined>;\n /**\n * Label placement signal. Defaults to `'inset'`.\n *\n * Not consumed by `createLabelState()` itself — visibility is driven solely by\n * `hasLabel`/`label`. Threaded through so component authors can apply\n * placement-specific styling (`label-placement` attribute) without a second\n * option; if placement-dependent visibility is ever needed, wire it into\n * `labelVisible` here instead of adding a new option.\n */\n labelPlacement?: Readable<LabelPlacement>;\n prefix?: string;\n};\n\nexport const createLabelState = (options: LabelStateOptions): LabelStateHandle => {\n const fieldId = options.id ?? createStableId(options.prefix ?? 'field');\n const labelId = createStableId('label');\n const label$ = options.label ?? computed(() => undefined);\n\n // Slot-first composition: if the caller provides `hasLabel`, use it instead\n // of deriving visibility purely from the label text signal. This allows\n // components with a `<slot name=\"label\">` to stay visible even when the\n // `label` prop is empty but a slotted label element is present.\n const labelVisible = options.hasLabel ?? computed(() => Boolean(label$.value));\n const ariaLabelledBy = computed(() => (labelVisible.value ? labelId : null));\n\n return { ariaLabelledBy, fieldId, labelId, labelVisible };\n};\n\n// ── Field handle (assistive + label, composed) ──────────────────────────────\n\n/**\n * Common handle returned by `createField` — the base for both `TextFieldHandle`\n * and `ChoiceFieldHandle`. Composes `createAssistiveState` + `createLabelState`;\n * call those directly instead if a field has no visible `<label>` (see\n * `createAssistiveState`'s doc comment).\n *\n * ARIA signals and label state are flat on the handle — no nested `aria` or\n * `label` sub-objects. `labelId`/`labelVisible`/`ariaLabelledBy` are always\n * present but only meaningful when `label`/`hasLabel` was passed — omit both\n * and they resolve to an always-hidden, unreferenced label.\n *\n * @example\n * ```html\n * <label id=\"${labelId}\" ?hidden=\"${() => !labelVisible.value}\">...</label>\n * <input\n * :aria-labelledby=\"${ariaLabelledBy}\"\n * :aria-describedby=\"${ariaDescribedBy}\"\n * :aria-invalid=\"${ariaInvalid}\" />\n * ```\n */\nexport type FieldHandle = AssistiveStateHandle & LabelStateHandle;\n\n/** Options shared by both `createTextField` and `createChoiceField`. */\nexport type FieldOptions = AssistiveStateOptions & LabelStateOptions;\n\nexport const createField = (options: FieldOptions): FieldHandle => ({\n ...createAssistiveState(options),\n ...createLabelState(options),\n});\n"],"mappings":";;;;AAsBA,IAAa,KAA0B,MACrC,SAAgB;CACd,WAAW,EAAQ,OAAO,SAAS;CACnC,YAAY,EAAQ,QAAQ,SAAS;AACvC,EAAE,GAyBS,KAAsB,MACjC,QAA6B;CAC3B,IAAM,IAAQ,EAAQ,OAAO,SAAS,IAChC,IAAY,EAAQ,WAAW,OAC/B,IAAkB,OAAO,CAAS,GAClC,IAAiB,OAAO,SAAS,CAAe,KAAK,IAAkB,IAAI,IAAkB,MAC7F,IAAa,MAAmB,MAChC,IAAc,IAAa,GAAG,EAAM,OAAO,KAAK,MAAmB,IACnE,IAAQ,IAAa,EAAM,SAAS,IAAiB;CAE3D,OAAO;EACL,gBAAgB,IAAa,KAAS,IAAI;EAC1C,kBAAkB,IAAa,KAAS,MAAO,IAAQ,IAAI;EAC3D;CACF;AACF,CAAC,GAYU,KAAoB,GAAmC,IAAO,cACpE,IAED,EAAQ,iBAAuB,GAAG,EAAK,aAEvC,EAAQ,mBAAyB,GAAG,EAAK,eAEtC,IANc,GA4DV,KAAwB,MAAyD;CAC5F,IAAM,IAAW,QAAe,EAAQ,EAAQ,UAAU,KAAM,GAC1D,IAAc,EAAe,QAAQ,GACrC,IAAU,EAAe,OAAO,GAChC,IAAoB,EAAuB;EAAE,OAAO,EAAQ;EAAO,QAAQ,EAAQ;CAAO,CAAC,GAE3F,IAAkB,QACtB,EAAkB,MAAM,aAAa,EAAkB,MAAM,aAAa,IAAc,IAC1F,GACM,IAAmB,QAAgB,EAAkB,MAAM,YAAY,IAAU,IAAK,GACtF,IAAc,QAA+B,EAAkB,MAAM,YAAY,SAAS,IAAK,GAEjG,IAA+C;CAsBnD,OAAO;EACL;EACA;EACA;EACA;EACA,kBA1BuB,MAAoD;GAC3E,IAAY;EACd;EAyBE;EACA;EACA,WAAW,QAAe,EAAkB,MAAM,SAAS;EAC3D,YAAY,QAAe,EAAkB,MAAM,UAAU;EAC7D,oBA3ByB,MAA4D;GACjF,MAAQ,YAAY,UAAU,GAElC;QAAI,CAAC,GAAW;KAId,EACE,2KACF;KAEA;IACF;IAEA,EAAU,eAAe;GAFzB;EAGF;CAaA;AACF,GAmDa,KAAoB,MAAiD;CAChF,IAAM,IAAU,EAAQ,MAAM,EAAe,EAAQ,UAAU,OAAO,GAChE,IAAU,EAAe,OAAO,GAChC,IAAS,EAAQ,SAAS,QAAe,KAAA,CAAS,GAMlD,IAAe,EAAQ,YAAY,QAAe,EAAQ,EAAO,KAAM;CAG7E,OAAO;EAAE,gBAFc,QAAgB,EAAa,QAAQ,IAAU,IAE7D;EAAgB;EAAS;EAAS;CAAa;AAC1D,GA6Ba,KAAe,OAAwC;CAClE,GAAG,EAAqB,CAAO;CAC/B,GAAG,EAAiB,CAAO;AAC7B"}
|
package/dist/headless/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export { lifecycleSignal } from './ore';
|
|
2
|
-
export {
|
|
2
|
+
export { createAutoResize, type AutoResizeControl, type AutoResizeOptions } from './auto-resize';
|
|
3
|
+
export { counterClassName, createAssistiveState, createErrorHelperState, createField, createLabelState, type AssistiveStateHandle, type AssistiveStateOptions, type ControlValidationMode, type CounterOptions, type CounterState, type ErrorHelperOptions, type ErrorHelperState, type FieldHandle, type FieldOptions, type LabelPlacement, type LabelStateHandle, type LabelStateOptions, type ValidationTrigger, } from './field-base';
|
|
3
4
|
export { createTextField, type TextFieldDetach, type TextFieldHandle, type TextFieldOptions } from './text-field';
|
|
4
5
|
export { createChoiceField, type ChoiceChangeDetail, type ChoiceFieldHandle, type ChoiceFieldOptions, } from './choice-field';
|
|
5
6
|
export { createCheckable, type CheckableChangePayload, type CheckableHandle, type CheckableOptions } from './checkable';
|
|
7
|
+
export { createComposerControl, type ComposerControl, type ComposerControlOptions, type SendShortcut, } from './composer';
|
|
6
8
|
export { type DialogCloseReason, type DropdownCloseReason, type OverlayCloseDetail, type OverlayControl, type OverlayOpenDetail, type OverlayOpenReason, } from './overlay';
|
|
7
9
|
export { createOptionList, type BaseOptionItem, type OptionListHandle, type OptionListOptions, type PlacementOptions, } from './option-list';
|
|
8
10
|
export { createPaginatedList, type PaginatedListHandle, type PaginatedListOptions } from './paginated-list';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/headless/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AAGxC,OAAO,EACL,sBAAsB,EACtB,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/headless/index.ts"],"names":[],"mappings":"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"}
|