@skdx/docs-kit 0.39.0 → 0.40.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.
Files changed (142) hide show
  1. package/dist/data/angular-inputs.json +1 -1
  2. package/dist/data/api-manifest.json +1 -1
  3. package/dist/shared/contracts/accordion.js +1 -1
  4. package/dist/shared/contracts/alert.js +1 -1
  5. package/dist/shared/contracts/autocomplete.d.ts +1 -1
  6. package/dist/shared/contracts/autocomplete.js +1 -1
  7. package/dist/shared/contracts/avatar.js +1 -1
  8. package/dist/shared/contracts/breadcrumb.js +1 -1
  9. package/dist/shared/contracts/button.js +1 -1
  10. package/dist/shared/contracts/calendar.js +1 -1
  11. package/dist/shared/contracts/card.js +1 -1
  12. package/dist/shared/contracts/carousel.js +1 -1
  13. package/dist/shared/contracts/checkbox-card.js +1 -1
  14. package/dist/shared/contracts/checkbox-group.js +1 -1
  15. package/dist/shared/contracts/collapsible.js +1 -1
  16. package/dist/shared/contracts/combobox.js +1 -1
  17. package/dist/shared/contracts/command.js +1 -1
  18. package/dist/shared/contracts/dashboard-layout.js +1 -1
  19. package/dist/shared/contracts/date-picker.js +1 -1
  20. package/dist/shared/contracts/dialog.d.ts +1 -1
  21. package/dist/shared/contracts/dialog.js +1 -1
  22. package/dist/shared/contracts/drawer.d.ts +1 -1
  23. package/dist/shared/contracts/drawer.js +1 -1
  24. package/dist/shared/contracts/field.js +1 -1
  25. package/dist/shared/contracts/file-upload.js +1 -1
  26. package/dist/shared/contracts/floating-panel.js +1 -1
  27. package/dist/shared/contracts/form.js +1 -1
  28. package/dist/shared/contracts/image.js +1 -1
  29. package/dist/shared/contracts/listbox.js +1 -1
  30. package/dist/shared/contracts/loading-overlay.js +1 -1
  31. package/dist/shared/contracts/mentions.js +1 -1
  32. package/dist/shared/contracts/menu.js +1 -1
  33. package/dist/shared/contracts/menubar.js +1 -1
  34. package/dist/shared/contracts/navigation-menu.js +1 -1
  35. package/dist/shared/contracts/pagination.js +1 -1
  36. package/dist/shared/contracts/password-input.js +1 -1
  37. package/dist/shared/contracts/popover.js +1 -1
  38. package/dist/shared/contracts/progress.js +1 -1
  39. package/dist/shared/contracts/qr-code.js +1 -1
  40. package/dist/shared/contracts/radio-card.js +1 -1
  41. package/dist/shared/contracts/radio-group.js +1 -1
  42. package/dist/shared/contracts/rating.js +1 -1
  43. package/dist/shared/contracts/resizable.js +1 -1
  44. package/dist/shared/contracts/scroll-area.js +1 -1
  45. package/dist/shared/contracts/select.js +1 -1
  46. package/dist/shared/contracts/slider.js +1 -1
  47. package/dist/shared/contracts/speed-dial.js +1 -1
  48. package/dist/shared/contracts/split-button.js +1 -1
  49. package/dist/shared/contracts/stepper.js +1 -1
  50. package/dist/shared/contracts/switch.js +1 -1
  51. package/dist/shared/contracts/table.js +3 -1
  52. package/dist/shared/contracts/tabs.js +1 -1
  53. package/dist/shared/contracts/tag-group.js +1 -1
  54. package/dist/shared/contracts/tags-input.js +1 -1
  55. package/dist/shared/contracts/text-field.js +1 -1
  56. package/dist/shared/contracts/textarea.js +1 -1
  57. package/dist/shared/contracts/timeline.js +1 -1
  58. package/dist/shared/contracts/toast.js +9 -1
  59. package/dist/shared/contracts/toolbar.js +1 -1
  60. package/dist/shared/contracts/tour.js +1 -1
  61. package/dist/shared/contracts/transfer.js +1 -1
  62. package/dist/shared/contracts/tree-select.js +1 -1
  63. package/dist/shared/contracts/tree.js +1 -1
  64. package/dist/shared/contracts/types.d.ts +2 -0
  65. package/dist/shared/contracts/workspace-switcher.js +1 -1
  66. package/dist/shared/core/field-core.d.ts +2 -0
  67. package/dist/shared/core/field-core.js +1 -1
  68. package/dist/skins-src/_foundation-fallback.css +2 -1
  69. package/dist/skins-src/components/_actuator.css +1 -1
  70. package/dist/skins-src/components/_card-control.css +1 -1
  71. package/dist/skins-src/components/_collection.css +2 -2
  72. package/dist/skins-src/components/_overlay.css +1 -1
  73. package/dist/skins-src/components/_trigger.css +1 -1
  74. package/dist/skins-src/components/accordion.css +1 -1
  75. package/dist/skins-src/components/alert.css +2 -2
  76. package/dist/skins-src/components/autocomplete.css +1 -1
  77. package/dist/skins-src/components/avatar.css +1 -1
  78. package/dist/skins-src/components/badge.css +1 -1
  79. package/dist/skins-src/components/button.css +1 -1
  80. package/dist/skins-src/components/calendar.css +8 -6
  81. package/dist/skins-src/components/card.css +1 -1
  82. package/dist/skins-src/components/carousel.css +1 -1
  83. package/dist/skins-src/components/chat-composer.css +1 -1
  84. package/dist/skins-src/components/chat-message.css +1 -1
  85. package/dist/skins-src/components/checkbox-card.css +2 -2
  86. package/dist/skins-src/components/checkbox.css +1 -1
  87. package/dist/skins-src/components/clipboard.css +2 -2
  88. package/dist/skins-src/components/color-picker.css +4 -4
  89. package/dist/skins-src/components/combobox.css +2 -2
  90. package/dist/skins-src/components/command.css +1 -1
  91. package/dist/skins-src/components/dashboard-layout.css +1 -1
  92. package/dist/skins-src/components/date-field.css +2 -2
  93. package/dist/skins-src/components/dialog.css +4 -4
  94. package/dist/skins-src/components/drawer.css +9 -9
  95. package/dist/skins-src/components/editable.css +3 -3
  96. package/dist/skins-src/components/file-upload.css +4 -4
  97. package/dist/skins-src/components/floating-panel.css +1 -1
  98. package/dist/skins-src/components/forgot-password-page.css +1 -1
  99. package/dist/skins-src/components/form-section.css +1 -1
  100. package/dist/skins-src/components/form.css +12 -2
  101. package/dist/skins-src/components/image.css +1 -1
  102. package/dist/skins-src/components/kbd.css +1 -1
  103. package/dist/skins-src/components/link.css +1 -1
  104. package/dist/skins-src/components/listbox.css +1 -1
  105. package/dist/skins-src/components/mentions.css +1 -1
  106. package/dist/skins-src/components/menubar.css +2 -2
  107. package/dist/skins-src/components/navigation-menu.css +1 -1
  108. package/dist/skins-src/components/number-field.css +1 -1
  109. package/dist/skins-src/components/onboarding-checklist.css +1 -1
  110. package/dist/skins-src/components/pagination.css +2 -2
  111. package/dist/skins-src/components/password-input.css +80 -16
  112. package/dist/skins-src/components/pin-input.css +2 -2
  113. package/dist/skins-src/components/popover.css +1 -1
  114. package/dist/skins-src/components/qr-code.css +2 -2
  115. package/dist/skins-src/components/radio-card.css +1 -1
  116. package/dist/skins-src/components/reset-password-page.css +1 -1
  117. package/dist/skins-src/components/select.css +3 -3
  118. package/dist/skins-src/components/sign-in-page.css +1 -1
  119. package/dist/skins-src/components/sign-up-page.css +1 -1
  120. package/dist/skins-src/components/skeleton.css +1 -1
  121. package/dist/skins-src/components/skip-link.css +1 -1
  122. package/dist/skins-src/components/speed-dial.css +3 -3
  123. package/dist/skins-src/components/stat-card.css +6 -1
  124. package/dist/skins-src/components/stepper.css +3 -1
  125. package/dist/skins-src/components/switch.css +21 -1
  126. package/dist/skins-src/components/table.css +4 -4
  127. package/dist/skins-src/components/tags-input.css +2 -2
  128. package/dist/skins-src/components/text-field.css +26 -21
  129. package/dist/skins-src/components/text.css +1 -1
  130. package/dist/skins-src/components/textarea.css +1 -1
  131. package/dist/skins-src/components/time-field.css +2 -2
  132. package/dist/skins-src/components/toast.css +2 -2
  133. package/dist/skins-src/components/toggle-group.css +3 -3
  134. package/dist/skins-src/components/toggle.css +1 -1
  135. package/dist/skins-src/components/toolbar.css +2 -2
  136. package/dist/skins-src/components/tree-select.css +2 -2
  137. package/dist/skins-src/components/tree.css +1 -1
  138. package/dist/skins-src/components/usage-card.css +1 -1
  139. package/dist/skins-src/components/user-menu.css +1 -1
  140. package/dist/skins-src/components/verification-page.css +1 -1
  141. package/dist/skins-src/components/workspace-switcher.css +2 -2
  142. package/package.json +14 -14
@@ -1 +1 @@
1
- import{changeDetail as e}from"./change-reason.js";import{constraintValidationMethods as t,setCustomValidityMethod as a}from"./form-validity.js";import{ROOT_DATA_ATTRIBUTES as r}from"./styling-hooks.js";var o={name:"Textarea",purpose:"A wrapper around a native <textarea>. With autoResize, grows to fit content instead of scrolling (optionally capped to maxRows). Native attributes (name, required, maxlength, etc.) are forwarded to the control.",lightDom:!0,props:[{name:"value",type:"string",required:!1,description:"Current value, for controlled usage. Bind it with the framework's native two-way mechanism; pair with the value-change event."},{name:"defaultValue",type:"string",required:!1,description:"Initial value, for uncontrolled usage \u2014 seeds the internally-tracked value."},{name:"autoResize",type:"boolean",defaultValue:"false",required:!1,description:"Grow the textarea to fit its content instead of showing a scrollbar."},{name:"maxRows",type:"number",required:!1,description:"Caps auto-resize growth to this many rows, based on computed line-height."},{name:"minRows",type:"number",defaultValue:"2",required:!1,description:"The control's opening size, and the floor auto-resize will not shrink below. Rendered as the native rows attribute when autoResize is off, and applied as a minimum on the measured height when it is on \u2014 so the same number means the same thing either way. Default 2, which is the platform's own rows default, so an unmarked textarea is unchanged. Where minRows and maxRows disagree, maxRows wins: an explicit cap is a stronger statement than the default floor."},{name:"invalid",type:"boolean",defaultValue:"false",required:!1,description:"Marks the textarea as invalid, exposed as aria-invalid/data-invalid."},{name:"disabled",type:"boolean",defaultValue:"false",required:!1,description:"Disables the textarea: it leaves the tab order, submits nothing, and is announced as unavailable."},{name:"readOnly",type:"boolean",defaultValue:"false",required:!1,description:"Makes the textarea read-only, exposed as the native readonly attribute plus aria-readonly/data-readonly."},{name:"name",type:"string",required:!1,description:"Form field name. Without it the textarea submits nothing, exactly like a nameless native <textarea>."},{name:"required",type:"boolean",defaultValue:"false",required:!1,description:"Marks the textarea required for native constraint validation (valueMissing when empty)."},{name:"form",type:"string",required:!1,description:`Id of the <form> this control submits with, so a textarea rendered outside that form's subtree still participates in it \u2014 a native <textarea form="\u2026">. React, Vue, Svelte and Angular put it on the <textarea> they render; <skdx-textarea> is form-associated through ElementInternals, which resolves the same attribute on the host, so the attribute is declared there and ElementInternals.form does the rest.`},{name:"maxLength",type:"number",required:!1,description:"Native maxlength \u2014 the longest value the control accepts, reported as tooLong. Declared because the contract had no length constraint at all, so the attribute reached the control only in the three packages that forward native attributes onto the element they render."},{name:"minLength",type:"number",required:!1,description:"Native minlength \u2014 the shortest value constraint validation will accept, reported as tooShort. It fires only after a user edit, so it is not a stand-in for required."},{name:"inputMode",type:"string",required:!1,frameworks:["svelte","angular","webComponents"],exception:"attribute-passthrough",frameworksReason:"React and Vue pass inputmode through as a native attribute of the rendered textarea rather than a declared prop.",description:"Virtual keyboard hint forwarded as the native inputmode attribute."},{name:"resize",type:"'none' | 'block' | 'auto' | (string & {})",required:!1,description:"Resize handle rung, written to data-resize on the root; the data-resize attribute may be set directly instead."}],events:[{name:"value-change",description:"Fired with the new value whenever it changes, controlled or not.",frameworks:["vue","angular","webComponents","svelte"],exception:"event-shape",frameworksReason:"React has no distinct value-change member to document: it realises the concept as the native onChange passthrough already covered by the element props table, not a second callback prop.",detail:e("string")}],slots:[],appearance:[{attribute:"data-resize",values:["none","block","auto"],stylesheet:"textarea.css"}],states:["default","invalid","readonly","disabled"],controlledUncontrolled:"Controlled via value + value-change, or uncontrolled via defaultValue. The underlying element is a native <textarea>, so the platform still handles editing \u2014 value/defaultValue only decide who owns the value.",formBehavior:"Form-associated: with a name, submits its text and mirrors the inner <textarea>'s own ValidityState, including required. A form reset restores `defaultValue` (falling back to the empty string) in all five packages \u2014 never the text the control happened to be mounted or last bound with. `form` associates the control with a <form> it is not nested inside, by id.",validationBehavior:"invalid is a purely presentational flag (aria-invalid/data-invalid) \u2014 pair with native constraint validation (required, minlength, maxlength) or a Form component. `invalid` on its own tells a screen-reader user *that* the field is wrong and never *why*, so a textarea marked invalid must also be pointed at the text that explains it. The association is `aria-describedby`, not `aria-errormessage`: see the Field contract's `accessibility.ariaAttributes`, which records the system-wide ruling against `aria-errormessage` and the three reasons behind it. Wrap the Textarea in a Field and that wiring is automatic \u2014 the Field emits an `error` part with a stable id, discovers the control it wraps, and merges that id into the control's `aria-describedby`. Outside a Field it is the consumer's obligation, and the contract states it here so it is not silently no one's.",accessibility:{role:"generic (native <textarea>)",keyboard:[],ariaAttributes:["aria-invalid reflects invalid","aria-readonly reflects readOnly","aria-describedby carries the error text whenever invalid is set \u2014 merged in by a wrapping Field, or set by the consumer outside one. Deliberately not aria-errormessage; the Field contract records why that attribute is unused system-wide"],focusManagement:"Native textarea focus behavior.",namingRequirement:"A <textarea> has role textbox and permits no other role, so its accessible name can only come from an associated <label>, an aria-label, or an aria-labelledby \u2014 a placeholder is not a name, it disappears as soon as the field has content."},styling:{cssParts:["textarea"],dataAttributes:[...r,"data-invalid","data-readonly","data-disabled","data-resize"],cssCustomProperties:[]},publicMethods:[...t,a]};export{o as textareaContract};
1
+ import{changeDetail as e}from"./change-reason.js";import{constraintValidationMethods as t,setCustomValidityMethod as a}from"./form-validity.js";import{ROOT_DATA_ATTRIBUTES as r}from"./styling-hooks.js";var s={name:"Textarea",purpose:"A wrapper around a native <textarea>. With autoResize, grows to fit content instead of scrolling (optionally capped to maxRows). Native attributes (name, required, maxlength, etc.) are forwarded to the control.",lightDom:!0,props:[{name:"value",type:"string",required:!1,description:"Current value, for controlled usage. Bind it with the framework's native two-way mechanism; pair with the value-change event."},{name:"defaultValue",type:"string",required:!1,description:"Initial value, for uncontrolled usage \u2014 seeds the internally-tracked value."},{name:"autoResize",type:"boolean",defaultValue:"false",required:!1,description:"Grow the textarea to fit its content instead of showing a scrollbar."},{name:"maxRows",type:"number",required:!1,description:"Caps auto-resize growth to this many rows, based on computed line-height."},{name:"minRows",type:"number",defaultValue:"2",required:!1,description:"The control's opening size, and the floor auto-resize will not shrink below. Rendered as the native rows attribute when autoResize is off, and applied as a minimum on the measured height when it is on \u2014 so the same number means the same thing either way. Default 2, which is the platform's own rows default, so an unmarked textarea is unchanged. Where minRows and maxRows disagree, maxRows wins: an explicit cap is a stronger statement than the default floor."},{name:"invalid",type:"boolean",defaultValue:"false",required:!1,description:"Marks the textarea as invalid, exposed as aria-invalid/data-invalid."},{name:"disabled",type:"boolean",defaultValue:"false",required:!1,description:"Disables the textarea: it leaves the tab order, submits nothing, and is announced as unavailable."},{name:"readOnly",type:"boolean",defaultValue:"false",required:!1,description:"Makes the textarea read-only, exposed as the native readonly attribute plus aria-readonly/data-readonly."},{name:"name",type:"string",required:!1,description:"Form field name. Without it the textarea submits nothing, exactly like a nameless native <textarea>."},{name:"required",type:"boolean",defaultValue:"false",required:!1,description:"Marks the textarea required for native constraint validation (valueMissing when empty)."},{name:"form",type:"string",required:!1,description:`Id of the <form> this control submits with, so a textarea rendered outside that form's subtree still participates in it \u2014 a native <textarea form="\u2026">. React, Vue, Svelte and Angular put it on the <textarea> they render; <skdx-textarea> is form-associated through ElementInternals, which resolves the same attribute on the host, so the attribute is declared there and ElementInternals.form does the rest.`},{name:"maxLength",type:"number",required:!1,description:"Native maxlength \u2014 the longest value the control accepts, reported as tooLong. Declared because the contract had no length constraint at all, so the attribute reached the control only in the three packages that forward native attributes onto the element they render."},{name:"minLength",type:"number",required:!1,description:"Native minlength \u2014 the shortest value constraint validation will accept, reported as tooShort. It fires only after a user edit, so it is not a stand-in for required."},{name:"inputMode",type:"string",required:!1,frameworks:["svelte","angular","webComponents"],exception:"attribute-passthrough",frameworksReason:"React and Vue pass inputmode through as a native attribute of the rendered textarea rather than a declared prop.",description:"Virtual keyboard hint forwarded as the native inputmode attribute."},{name:"resize",type:"'none' | 'block' | 'auto' | (string & {})",required:!1,description:"Resize handle rung, written to data-resize on the root; the data-resize attribute may be set directly instead."}],events:[{name:"value-change",description:"Fired with the new value whenever it changes, controlled or not.",frameworks:["vue","angular","webComponents","svelte"],exception:"event-shape",frameworksReason:"React has no distinct value-change member to document: it realises the concept as the native onChange passthrough already covered by the element props table, not a second callback prop.",detail:e("string")}],slots:[],appearance:[{attribute:"data-resize",values:["none","block","auto"],stylesheet:"textarea.css"}],states:["default","invalid","readonly","disabled"],controlledUncontrolled:"Controlled via value + value-change, or uncontrolled via defaultValue. The underlying element is a native <textarea>, so the platform still handles editing \u2014 value/defaultValue only decide who owns the value.",formBehavior:"Form-associated: with a name, submits its text and mirrors the inner <textarea>'s own ValidityState, including required. A form reset restores `defaultValue` (falling back to the empty string) in all five packages \u2014 never the text the control happened to be mounted or last bound with. `form` associates the control with a <form> it is not nested inside, by id.",validationBehavior:"invalid is a purely presentational flag (aria-invalid/data-invalid) \u2014 pair with native constraint validation (required, minlength, maxlength) or a Form component. `invalid` on its own tells a screen-reader user *that* the field is wrong and never *why*, so a textarea marked invalid must also be pointed at the text that explains it. The association is `aria-describedby`, not `aria-errormessage`: see the Field contract's `accessibility.ariaAttributes`, which records the system-wide ruling against `aria-errormessage` and the three reasons behind it. Wrap the Textarea in a Field and that wiring is automatic \u2014 the Field emits an `error` part with a stable id, discovers the control it wraps, and merges that id into the control's `aria-describedby`. Outside a Field it is the consumer's obligation, and the contract states it here so it is not silently no one's.",accessibility:{role:"generic (native <textarea>)",keyboard:[],ariaAttributes:["aria-invalid reflects invalid","aria-readonly reflects readOnly","aria-describedby carries the error text whenever invalid is set \u2014 merged in by a wrapping Field, or set by the consumer outside one. Deliberately not aria-errormessage; the Field contract records why that attribute is unused system-wide"],focusManagement:"Native textarea focus behavior.",namingRequirement:"A <textarea> has role textbox and permits no other role, so its accessible name can only come from an associated <label>, an aria-label, or an aria-labelledby \u2014 a placeholder is not a name, it disappears as soon as the field has content."},styling:{cssParts:["textarea"],dataAttributes:[...r,"data-invalid","data-readonly","data-disabled","data-resize"],cssCustomProperties:[]},publicMethods:[...t,a],frameworkExtensions:[{framework:"angular",name:"autocomplete",type:"string",description:"Native <textarea> autocomplete binding on angular textarea. Native `autocomplete`, e.g. `'off'` or `'street-address'`. This wrapper declares the native binding explicitly; other wrappers inherit native-element attributes rather than duplicate each DOM attribute as a component prop.",kind:"prop"},{framework:"webComponents",name:"autocomplete",type:"string | undefined",description:"Native <textarea> autocomplete binding on webComponents textarea. Native `autocomplete`, e.g. `'off'` or `'street-address'`. This wrapper declares the native binding explicitly; other wrappers inherit native-element attributes rather than duplicate each DOM attribute as a component prop.",kind:"prop"},{framework:"angular",name:"placeholder",type:"string",description:"Native <textarea> placeholder binding on angular textarea. Native placeholder text shown when the textarea is empty. This wrapper declares the native binding explicitly; other wrappers inherit native-element attributes rather than duplicate each DOM attribute as a component prop.",kind:"prop"},{framework:"webComponents",name:"placeholder",type:"string | undefined",description:"Native <textarea> placeholder binding on webComponents textarea. Placeholder text passed straight through to the native textarea. This wrapper declares the native binding explicitly; other wrappers inherit native-element attributes rather than duplicate each DOM attribute as a component prop.",kind:"prop"},{framework:"angular",name:"rows",type:"number",description:"Native <textarea> rows binding on angular textarea. Native `rows` \u2014 the control's initial visible line count. This wrapper declares the native binding explicitly; other wrappers inherit native-element attributes rather than duplicate each DOM attribute as a component prop.",kind:"prop"},{framework:"webComponents",name:"rows",type:"number | undefined",description:"Native <textarea> rows binding on webComponents textarea. Native `rows` \u2014 the control's initial visible line count. This wrapper declares the native binding explicitly; other wrappers inherit native-element attributes rather than duplicate each DOM attribute as a component prop.",kind:"prop"},{framework:"webComponents",name:"spellCheck",type:"string | undefined",description:"Native <textarea> spell-check binding on webComponents textarea. Native `spellcheck` as a string; enumerated (`'true'`/`'false'`/inherit).\nForwarded to the inner control (not reflected on host). This wrapper declares the native binding explicitly; other wrappers inherit native-element attributes rather than duplicate each DOM attribute as a component prop.",kind:"prop"},{framework:"angular",name:"spellcheck",type:"string",description:"Native <textarea> spellcheck binding on angular textarea. Native `spellcheck`, as the string the attribute takes (`'true'`/`'false'`). This wrapper declares the native binding explicitly; other wrappers inherit native-element attributes rather than duplicate each DOM attribute as a component prop.",kind:"prop"},{framework:"angular",name:"wrap",type:"string",description:"Native <textarea> wrap binding on angular textarea. Native `wrap`: `'soft'`, `'hard'` or `'off'`, deciding whether line breaks are submitted. This wrapper declares the native binding explicitly; other wrappers inherit native-element attributes rather than duplicate each DOM attribute as a component prop.",kind:"prop"},{framework:"webComponents",name:"wrap",type:"string | undefined",description:"Native <textarea> wrap binding on webComponents textarea. Native `wrap`: `'soft'`, `'hard'` or `'off'`, deciding whether line breaks are submitted. This wrapper declares the native binding explicitly; other wrappers inherit native-element attributes rather than duplicate each DOM attribute as a component prop.",kind:"prop"},{framework:"webComponents",kind:"event",name:"input",type:"Content projection",description:"Re-dispatched from the inner textarea element. This element exposes its native/custom bubbling event to addEventListener consumers; other wrappers integrate the corresponding action through callback props or native DOM listeners. This records the element event spelling without inventing an emitted event on the other wrappers."},{framework:"webComponents",kind:"event",name:"input",type:"Content projection",description:"Re-dispatched from the inner textarea element. This element exposes its native/custom bubbling event to addEventListener consumers; other wrappers integrate the corresponding action through callback props or native DOM listeners. This records the element event spelling without inventing an emitted event on the other wrappers."}]};export{s as textareaContract};
@@ -1 +1 @@
1
- import{ROOT_DATA_ATTRIBUTES as e}from"./styling-hooks.js";var n={name:"Timeline",purpose:"A purely structural timeline of events. Renders an <ol>; layout and connecting lines are entirely up to consumer CSS. Composed of Item, Indicator, Divider, and Content parts.",props:[{name:"orientation",type:"'horizontal' | 'vertical'",defaultValue:"'vertical'",required:!1,description:"Layout axis, exposed as data-orientation only \u2014 no ARIA orientation or keyboard handling, since neither changes how the list is read. Both values are implemented via CSS."},{name:"items",type:"ReadonlyArray<{ content: unknown; opposite?: unknown; indicator?: unknown; indicatorLabel?: string; state?: 'complete' | 'current' | 'pending' }>",required:!1,description:"Event data for self-rendering instead of hand-written children: `content` is the body, `opposite` the metadata across the marker, `indicator` what goes in the marker, `state` the entry's place in the sequence (written to Item's data-state), and `indicatorLabel` names the marker (per-event, not per-root). Composition is the default; when supplied this is the source of truth."},{name:"renderContent",type:"(item: { content: unknown; opposite?: unknown; indicator?: unknown; indicatorLabel?: string; state?: string }) => unknown",required:!1,frameworks:["react","svelte","angular","webComponents"],exception:"render-prop",frameworksReason:"Vue and Angular have no render-function member to name: Vue spells this as a `content` scoped slot receiving the same entry, and Angular as a TemplateRef taking it as template context. It is the same content hole in each framework's own idiom \u2014 the gate matches member names literally and has no per-prop rename mechanism, which is how tree's `renderLabel` is already declared.",description:"Renders an event's body from its `items` entry, for content beyond the entry's `content` value. Ignored without `items`. Vue ships this as a `content` scoped slot; Angular as a TemplateRef. Property-only in Web Components (no attribute form for functions)."},{name:"renderOpposite",type:"(item: { content: unknown; opposite?: unknown; indicator?: unknown; indicatorLabel?: string; state?: string }, index: number) => unknown",required:!1,frameworks:["webComponents"],exception:"render-prop",frameworksReason:"React, Svelte and Angular put markup straight into an item's `opposite` as a node, snippet or template, and Vue spells it as the `opposite` scoped slot. A custom element's `items` property carries strings, so markup for the opposite region reaches it only through a render callback.",description:"Renders an event's opposite metadata in place of its `opposite` string, for markup rather than text. Ignored without `items`. Property-only, since a function has no attribute form."},{name:"align",type:"'end' | 'alternate' | (string & {})",required:!1,description:"Marker-side rung, written to data-align on the root; the data-align attribute may be set directly instead."}],events:[],parts:[{name:"Item",description:'One event. Composes an Indicator, an optional Divider, a Content and an optional Opposite, in that order \u2014 the skin places by grid column rather than by DOM order, so DOM order is free to be the reading order. Carries an explicit role="listitem", because the skin gives it display: grid and an item that is no longer display: list-item loses its implicit role in Safari/VoiceOver.',props:[{name:"state",type:"'complete' | 'current' | 'pending'",required:!1,description:"Where the event sits in the sequence, written to data-state on the item; the data-state attribute may be set directly instead."}]},{name:"Indicator",description:'The marker: a dot or custom content. Decorative by default (aria-hidden="true" when empty), since an announced dot before every event is noise. When rendering content, it must carry an accessible name (a meaningful icon with no name announces as nothing useful).'},{name:"Separator",description:'The connector between two markers. role="presentation" + aria-hidden="true": it is a line, and a line has nothing to say.'},{name:"Content",description:"The event itself \u2014 the Item's body content."},{name:"Opposite",description:"The metadata region on the far side of the marker (timestamp, actor, elapsed time). Optional. Exists because metadata would otherwise live inside Content; here the skin gives it its own column. Render before the Indicator: the skin places by grid column, but screen readers read DOM order."}],slots:[{name:"default",description:"Timeline items. Item elements and nothing else \u2014 anything else breaks the <ol> content model, which is what carries the list semantics this family has instead of a role of its own."},{name:"indicator",description:"A TimelineItem's dot/icon marker."},{name:"separator",description:"The connecting line between two TimelineItems."},{name:"content",description:"A TimelineItem's body content."},{name:"opposite",description:"A TimelineItem's metadata region, opposite the event across the marker."}],appearance:[{attribute:"data-align",values:["end","alternate"],stylesheet:"timeline.css"}],states:["complete","current","pending"],controlledUncontrolled:"Stateless \u2014 Timeline holds no internal state.",formBehavior:"Not applicable \u2014 Timeline does not participate in forms.",validationBehavior:"Not applicable \u2014 Timeline carries no value and participates in no validation.",accessibility:{role:'list (native <ol>, with an explicit role="list"); each item is an explicit role="listitem"',keyboard:[],ariaAttributes:['Root carries an explicit role="list", and each item an explicit role="listitem"','Separator is role="presentation" + aria-hidden="true"','Indicator is aria-hidden="true" when it renders nothing, and carries its own accessible name when it renders content'],focusManagement:"Not focusable; not part of the tab order."},styling:{cssParts:["timeline","item","indicator","separator","content","opposite"],dataAttributes:[...e,"data-orientation","data-align","data-state",{name:"data-skdx-timeline-generated",frameworks:["webComponents"],exception:"wc-marker",frameworksReason:'Marks a node the element generated for itself rather than one the consumer authored. React, Vue, Svelte and Angular re-render their derived anatomy declaratively, so "mine or theirs" is answered by the framework and no marker is needed.'}],cssCustomProperties:[]},publicMethods:[]};export{n as timelineContract};
1
+ import{ROOT_DATA_ATTRIBUTES as e}from"./styling-hooks.js";var n={name:"Timeline",purpose:"A purely structural timeline of events. Renders an <ol>; layout and connecting lines are entirely up to consumer CSS. Composed of Item, Indicator, Divider, and Content parts.",props:[{name:"orientation",type:"'horizontal' | 'vertical'",defaultValue:"'vertical'",required:!1,description:"Layout axis, exposed as data-orientation only \u2014 no ARIA orientation or keyboard handling, since neither changes how the list is read. Both values are implemented via CSS."},{name:"items",type:"ReadonlyArray<{ content: unknown; opposite?: unknown; indicator?: unknown; indicatorLabel?: string; state?: 'complete' | 'current' | 'pending' }>",required:!1,description:"Event data for self-rendering instead of hand-written children: `content` is the body, `opposite` the metadata across the marker, `indicator` what goes in the marker, `state` the entry's place in the sequence (written to Item's data-state), and `indicatorLabel` names the marker (per-event, not per-root). Composition is the default; when supplied this is the source of truth."},{name:"renderContent",type:"(item: { content: unknown; opposite?: unknown; indicator?: unknown; indicatorLabel?: string; state?: string }) => unknown",required:!1,frameworks:["react","svelte","angular","webComponents"],exception:"render-prop",frameworksReason:"Vue and Angular have no render-function member to name: Vue spells this as a `content` scoped slot receiving the same entry, and Angular as a TemplateRef taking it as template context. It is the same content hole in each framework's own idiom \u2014 the gate matches member names literally and has no per-prop rename mechanism, which is how tree's `renderLabel` is already declared.",description:"Renders an event's body from its `items` entry, for content beyond the entry's `content` value. Ignored without `items`. Vue ships this as a `content` scoped slot; Angular as a TemplateRef. Property-only in Web Components (no attribute form for functions)."},{name:"renderOpposite",type:"(item: { content: unknown; opposite?: unknown; indicator?: unknown; indicatorLabel?: string; state?: string }, index: number) => unknown",required:!1,frameworks:["webComponents"],exception:"render-prop",frameworksReason:"React, Svelte and Angular put markup straight into an item's `opposite` as a node, snippet or template, and Vue spells it as the `opposite` scoped slot. A custom element's `items` property carries strings, so markup for the opposite region reaches it only through a render callback.",description:"Renders an event's opposite metadata in place of its `opposite` string, for markup rather than text. Ignored without `items`. Property-only, since a function has no attribute form."},{name:"align",type:"'end' | 'alternate' | (string & {})",required:!1,description:"Marker-side rung, written to data-align on the root; the data-align attribute may be set directly instead."}],events:[],parts:[{name:"Item",description:'One event. Composes an Indicator, an optional Divider, a Content and an optional Opposite, in that order \u2014 the skin places by grid column rather than by DOM order, so DOM order is free to be the reading order. Carries an explicit role="listitem", because the skin gives it display: grid and an item that is no longer display: list-item loses its implicit role in Safari/VoiceOver.',props:[{name:"state",type:"'complete' | 'current' | 'pending'",required:!1,description:"Where the event sits in the sequence, written to data-state on the item; the data-state attribute may be set directly instead."}]},{name:"Indicator",description:'The marker: a dot or custom content. Decorative by default (aria-hidden="true" when empty), since an announced dot before every event is noise. When rendering content, it must carry an accessible name (a meaningful icon with no name announces as nothing useful).',slots:[{name:"default",description:"A TimelineItem's dot/icon marker."}]},{name:"Separator",description:'The connector between two markers. role="presentation" + aria-hidden="true": it is a line, and a line has nothing to say.',slots:[{name:"default",description:"The connecting line between two TimelineItems."}]},{name:"Content",description:"The event itself \u2014 the Item's body content.",slots:[{name:"default",description:"A TimelineItem's body content."}]},{name:"Opposite",description:"The metadata region on the far side of the marker (timestamp, actor, elapsed time). Optional. Exists because metadata would otherwise live inside Content; here the skin gives it its own column. Render before the Indicator: the skin places by grid column, but screen readers read DOM order.",slots:[{name:"default",description:"A TimelineItem's metadata region, opposite the event across the marker."}]}],slots:[{name:"default",description:"Timeline items. Item elements and nothing else \u2014 anything else breaks the <ol> content model, which is what carries the list semantics this family has instead of a role of its own."},{name:"content",description:"A TimelineItem's body content.",frameworks:["vue"],exception:"derived-anatomy",frameworksReason:"The root exposes content as content projection in vue. Other wrappers project default content into the Content part or use their separately documented rendering property; they do not accept a root named slot with this spelling."},{name:"opposite",description:"A TimelineItem's metadata region, opposite the event across the marker.",frameworks:["vue"],exception:"derived-anatomy",frameworksReason:"The root exposes opposite as content projection in vue. Other wrappers project default content into the Opposite part or use their separately documented rendering property; they do not accept a root named slot with this spelling."},{name:"render-content",frameworks:["svelte"],exception:"render-prop",frameworksReason:"svelte expose render-content as native content projection for the renderContent rendering capability. Other frameworks pass the documented renderContent render prop or template instead of a named slot with this spelling.",description:"Renders an event's body from its `items` entry, for content beyond the entry's `content` value. Ignored without `items`. Vue ships this as a `content` scoped slot; Angular as a TemplateRef. Property-only in Web Components (no attribute form for functions)."}],appearance:[{attribute:"data-align",values:["end","alternate"],stylesheet:"timeline.css"}],states:["complete","current","pending"],controlledUncontrolled:"Stateless \u2014 Timeline holds no internal state.",formBehavior:"Not applicable \u2014 Timeline does not participate in forms.",validationBehavior:"Not applicable \u2014 Timeline carries no value and participates in no validation.",accessibility:{role:'list (native <ol>, with an explicit role="list"); each item is an explicit role="listitem"',keyboard:[],ariaAttributes:['Root carries an explicit role="list", and each item an explicit role="listitem"','Separator is role="presentation" + aria-hidden="true"','Indicator is aria-hidden="true" when it renders nothing, and carries its own accessible name when it renders content'],focusManagement:"Not focusable; not part of the tab order."},styling:{cssParts:["timeline","item","indicator","separator","content","opposite"],dataAttributes:[...e,"data-orientation","data-align","data-state",{name:"data-skdx-timeline-generated",frameworks:["webComponents"],exception:"wc-marker",frameworksReason:'Marks a node the element generated for itself rather than one the consumer authored. React, Vue, Svelte and Angular re-render their derived anatomy declaratively, so "mine or theirs" is answered by the framework and no marker is needed.'}],cssCustomProperties:[]},publicMethods:[]};export{n as timelineContract};
@@ -1 +1,9 @@
1
- import{forceMountProp as e}from"./overlay-parts.js";import{ROOT_DATA_ATTRIBUTES as t}from"./styling-hooks.js";var a={...e,description:`Keep the toast in the DOM while closed so an exit animation has a data-state="closed" element. Pair with open-change-complete to drop the entry from the consumer's list.`,frameworks:["react","vue","svelte","angular"],exception:"force-mount",frameworksReason:"Web Components toggles `hidden` instead of unmounting, so a mount switch would have no state."},r={name:"Toast",purpose:"Accessible, auto-dismissing notifications. ToastProvider renders an aria-live announcer; Toasts auto-dismiss unless hovered or focused. The toaster store (createToaster/SkdxToastService) queues entries and publishes create/update/dismiss/promise.",props:[{name:"open",type:"boolean",required:!1,description:"Controlled open state, on Toast itself. Pair with the open-change event."},{name:"defaultOpen",type:"boolean",defaultValue:"true",required:!1,description:"Initial open state for uncontrolled usage. Defaults to true: a toast mounts after an event has already occurred."},{name:"duration",type:"number",required:!1,description:"Auto-dismiss duration in ms for this Toast. Infinity disables auto-dismiss. Falls back to the ancestor ToastProvider's duration when unset."},a,{name:"type",type:"'foreground' | 'background'",defaultValue:"'background'",required:!1,description:`"foreground" announces assertively; "background" politely. Picks which of ToastProvider's two announcer regions the toast enters. The root carries no live-region role.`}],events:[{name:"open-change",detail:"boolean",description:"Fired with the new open state when it changes: timer expiring, Close, Escape, or swipe past swipeThreshold. A swipe routes through open-change for unified handling."},{name:"open-change-complete",detail:"boolean",description:"Fired after the toast's enter or exit animation ends, or immediately when none. Signals when a forceMounted toast should be dropped from the consumer's list."},{name:"escape-key-down",detail:"Event",description:"Fired when Escape is pressed while the toast has focus. Preventing default keeps the toast open."},{name:"pause",detail:"void",description:"Fired when the auto-dismiss timer pauses: pointer enters, focus lands inside, or page is hidden. Not cancelable (WCAG 2.2 SC 2.2.1)."},{name:"resume",detail:"void",description:"Fired when the auto-dismiss timer restarts on the remaining time: the pointer leaves, focus leaves, or the page becomes visible again. Not cancelable."}],parts:[{name:"Provider",description:"App-level ancestor mounted once near root. Supplies default auto-dismiss duration and region label, and renders two persistent aria-live regions that announce toasts.",props:[{name:"label",type:"string",defaultValue:"'Notification'",required:!1,description:"Accessible name for the ToastViewport landmark. Store-rendered regions use 'Notifications'; hand-mounted Providers use 'Notification'."},{name:"duration",type:"number",defaultValue:"5000",required:!1,description:"Default auto-dismiss duration (ms) for descendant Toasts. 5000 is a floor: WCAG 2.2.1 requires time to read; timer pauses on hover/focus/page-hidden."},{name:"swipeDirection",type:"'up' | 'down' | 'left' | 'right' | 'start' | 'end'",defaultValue:"'end'",required:!1,description:"Drag direction to dismiss. start/end resolve against writing direction and mirror under RTL; the four physical values are absolute."},{name:"swipeThreshold",type:"number",defaultValue:"50",required:!1,description:'Pixels along swipeDirection to dismiss. Short of it, the drag snaps back (data-swipe="cancel").'}]},{name:"Toaster",aliases:[{name:"Region",frameworks:["webComponents"],frameworksReason:"A custom element is named by its tag, and <skdx-toaster> would read as the store rather than the landmark it renders; <skdx-toast-region> names the region a page mounts once."}],description:"The region component that renders at most `max` entries from the store into a ToastProvider + ToastViewport. React/Vue/Svelte/Web Components take the store as a `toaster` prop; Angular injects SkdxToastService instead. Takes no props: max/placement/duration/label are set on the store (see contextApi). Translates each entry's type onto data-type (queue's vocabulary) and data-status (severity vocabulary for styling)."},{name:"Viewport",description:'Fixed region the toasts stack in: role="region", labelled by Provider, tabindex="-1", data-placement for docking.',props:[{name:"hotkey",type:"string[]",defaultValue:"['F6']",required:!1,description:"Keys to move focus into the region and (with Shift) back out. Modifiers (Alt/Control/Meta/Shift) precede the KeyboardEvent.key. Appended to the region's accessible name so users know about it."}]},{name:"Title",description:"The toast's heading, published as aria-labelledby for keyboard entry."},{name:"Description",description:"The toast's body text, published as aria-describedby."},{name:"Action",description:'Optional actionable button ("Undo"). Runs the handler then closes the toast.',props:[{name:"altText",type:"string",required:!0,description:'What the action does (e.g., "Undo upload"). Required: the verb alone means nothing after the toast disappears.'}]},{name:"Close",description:'Dismissal button. Defaults to aria-label="Dismiss".'}],slots:[{name:"default",description:"Toast's body (Title, Description, Action, Close)."}],states:["open","closed","queued"],controlledUncontrolled:"Uncontrolled by default: starts open and self-closes. Controlled via open prop.",formBehavior:'Not applicable \u2014 Toast participates in no form; Action and Close are type="button".',validationBehavior:"Not applicable.",accessibility:{role:`none \u2014 the toast root is a generic element; announcement is the ToastProvider announcer's job, and a role="status"/"alert" here would duplicate it. The one carve-out: a standalone <skdx-toast> with no provider above it sets role="status" on itself, because with no announcer in the tree the host is the only channel left`,keyboard:[{key:"Escape",behavior:"Closes the toast."},{key:"Tab",behavior:"Reaches Action/Close while the toast is visible; pauses the auto-dismiss timer while any part of it is focused."},{key:"F6 (ToastViewport's hotkey)",behavior:"Moves focus into the ToastViewport region from anywhere on the page \u2014 the way in for a keyboard user, since a toast never steals focus. Configurable per region via the Viewport part's hotkey, and appended to the region's accessible name so it is announced on arrival."},{key:"Shift+F6 (Shift + ToastViewport's hotkey)",behavior:"Returns focus to its previous location."}],ariaAttributes:["ToastProvider renders two persistent visually-hidden aria-live regions (polite and assertive) that announce toasts",'ToastViewport is role="region", labelled by ToastProvider with hotkey appended ("Notifications (F6)"), tabindex="-1"',"Toast root carries no live-region role: its text reaches exactly one aria-live region (the announcer)","Toast root aria-labelledby points at Title, aria-describedby at Description (keyboard entry users benefit)",'ToastClose carries aria-label="Dismiss" by default',"ToastAction carries its altText as aria-label"],namingRequirement:'ToastClose must have an accessible name. Defaults to aria-label="Dismiss" so screen-reader users can dismiss it.',focusManagement:"Does not steal focus \u2014 announced via aria-live instead. F6 enters the region, Shift+F6 leaves. Focus restores if the focused toast is removed. Hover/focus/page-hidden pauses the timer. Swipe is addition to Close/Escape/timer (WCAG 2.2 SC 2.5.1, 2.5.7)."},styling:{cssParts:["viewport","root","title","description","action","close"],dataAttributes:[...t,"data-state","data-type","data-status","data-paused","data-swipe","data-swipe-direction","data-placement","data-skdx-toast-announcer"],cssCustomProperties:["--skdx-toast-swipe-move-x","--skdx-toast-swipe-move-y"]},publicMethods:[{name:"setOpen",signature:"(open: boolean) => void",frameworks:["webComponents"],exception:"imperative",frameworksReason:"Only the custom element publishes it. The other four re-render with a new open prop and never expose a setter; a Web Components consumer holds the element itself, so the same change is made by calling this method on it.",description:"Shows or dismisses this toast and emits open-change, exactly as its close control would."}],contextApi:[{name:"createToaster",signature:"(options?: { max?: number; placement?: ToastPlacement; duration?: number; label?: string }) => ToasterStore",description:"Creates the queue once per app. Options are read-only on the store; no component accepts them as props.",accessors:{react:"createToaster({ max, placement, duration, label }) from '@skdx/react'",vue:"createToaster({ max, placement, duration, label }) from '@skdx/vue'",svelte:"createToaster({ max, placement, duration, label }) from '@skdx/svelte'",angular:"{ provide: SKDX_TOASTER_OPTIONS, useValue: { max, placement, duration, label } } in the root providers",webComponents:"createToaster({ max, placement, duration, label }) from '@skdx/web-components', assigned to <skdx-toast-region>.toaster"}},{name:"max",signature:"readonly max: number",defaultValue:"3",description:"Visible toasts at once; rest queue in FIFO order. Set in createToaster/SKDX_TOASTER_OPTIONS.",accessors:{react:"createToaster().max",vue:"createToaster().max",svelte:"createToaster().max",angular:"inject(SkdxToastService).max",webComponents:"createToaster().max"}},{name:"placement",signature:"readonly placement: 'top-start' | 'top' | 'top-end' | 'bottom-start' | 'bottom' | 'bottom-end'",defaultValue:"'bottom-end'",description:"Region position, emitted as data-placement for layout. Set in createToaster/SKDX_TOASTER_OPTIONS.",accessors:{react:"createToaster().placement",vue:"createToaster().placement",svelte:"createToaster().placement",angular:"inject(SkdxToastService).placement",webComponents:"createToaster().placement"}},{name:"duration",signature:"readonly duration: number",defaultValue:"5000",description:"Default auto-dismiss duration (ms) for queued toasts, minimum 5000: WCAG 2.2.1. Loading toasts never auto-dismiss. Set in createToaster/SKDX_TOASTER_OPTIONS.",accessors:{react:"createToaster().duration",vue:"createToaster().duration",svelte:"createToaster().duration",angular:"inject(SkdxToastService).duration",webComponents:"createToaster().duration"}},{name:"label",signature:"readonly label: string",defaultValue:"'Notifications'",description:"Accessible label for the region landmark. Set in createToaster/SKDX_TOASTER_OPTIONS.",accessors:{react:"createToaster().label",vue:"createToaster().label",svelte:"createToaster().label",angular:"inject(SkdxToastService).label",webComponents:"createToaster().label"}},{name:"create",signature:"(options?: { id?, title?, description?, type?, duration?, action?, closeLabel? }) => string",description:"Queues a toast and returns its id. Creating with an existing id replaces that entry.",accessors:{react:"createToaster().create",vue:"createToaster().create",svelte:"createToaster().create",angular:"inject(SkdxToastService).create",webComponents:"createToaster().create"}},{name:"update",signature:"(id: string, options: ToastOptions) => void",description:"Merges options into a queued toast and re-announces it.",accessors:{react:"createToaster().update",vue:"createToaster().update",svelte:"createToaster().update",angular:"inject(SkdxToastService).update",webComponents:"createToaster().update"}},{name:"dismiss",signature:"(id?: string) => void",description:"Removes one toast or the entire queue.",accessors:{react:"createToaster().dismiss",vue:"createToaster().dismiss",svelte:"createToaster().dismiss",angular:"inject(SkdxToastService).dismiss",webComponents:"createToaster().dismiss"}},{name:"promise",signature:"<T>(promise: Promise<T>, messages: { loading, success, error }, options?: ToastOptions) => Promise<T>",description:"Shows a loading toast, then success or error when the promise settles. Rejections are re-thrown.",accessors:{react:"createToaster().promise",vue:"createToaster().promise",svelte:"createToaster().promise",angular:"inject(SkdxToastService).promise",webComponents:"createToaster().promise"}}]};export{r as toastContract};
1
+ import{forceMountProp as e}from"./overlay-parts.js";import{ROOT_DATA_ATTRIBUTES as t}from"./styling-hooks.js";var a={...e,description:`Keep the toast in the DOM while closed so an exit animation has a data-state="closed" element. Pair with open-change-complete to drop the entry from the consumer's list.`,frameworks:["react","vue","svelte","angular"],exception:"force-mount",frameworksReason:"Web Components toggles `hidden` instead of unmounting, so a mount switch would have no state."},s={name:"Toast",purpose:"Accessible, auto-dismissing notifications. ToastProvider renders an aria-live announcer; Toasts auto-dismiss unless hovered or focused. The toaster store (createToaster/SkdxToastService) queues entries and publishes create/update/dismiss/promise.",props:[{name:"open",type:"boolean",required:!1,description:"Controlled open state, on Toast itself. Pair with the open-change event."},{name:"defaultOpen",type:"boolean",defaultValue:"true",required:!1,description:"Initial open state for uncontrolled usage. Defaults to true: a toast mounts after an event has already occurred."},{name:"duration",type:"number",required:!1,description:"Auto-dismiss duration in ms for this Toast. Infinity disables auto-dismiss. Falls back to the ancestor ToastProvider's duration when unset."},a,{name:"type",type:"'foreground' | 'background'",defaultValue:"'background'",required:!1,description:`"foreground" announces assertively; "background" politely. Picks which of ToastProvider's two announcer regions the toast enters. The root carries no live-region role.`}],events:[{name:"open-change",detail:"boolean",description:"Fired with the new open state when it changes: timer expiring, Close, Escape, or swipe past swipeThreshold. A swipe routes through open-change for unified handling."},{name:"open-change-complete",detail:"boolean",description:"Fired after the toast's enter or exit animation ends, or immediately when none. Signals when a forceMounted toast should be dropped from the consumer's list."},{name:"escape-key-down",detail:"Event",description:"Fired when Escape is pressed while the toast has focus. Preventing default keeps the toast open."},{name:"pause",detail:"void",description:"Fired when the auto-dismiss timer pauses: pointer enters, focus lands inside, or page is hidden. Not cancelable (WCAG 2.2 SC 2.2.1)."},{name:"resume",detail:"void",description:"Fired when the auto-dismiss timer restarts on the remaining time: the pointer leaves, focus leaves, or the page becomes visible again. Not cancelable."}],parts:[{name:"Provider",description:"App-level ancestor mounted once near root. Supplies default auto-dismiss duration and region label, and renders two persistent aria-live regions that announce toasts.",props:[{name:"label",type:"string",defaultValue:"'Notification'",required:!1,description:"Accessible name for the ToastViewport landmark. Store-rendered regions use 'Notifications'; hand-mounted Providers use 'Notification'."},{name:"duration",type:"number",defaultValue:"5000",required:!1,description:"Default auto-dismiss duration (ms) for descendant Toasts. 5000 is a floor: WCAG 2.2.1 requires time to read; timer pauses on hover/focus/page-hidden."},{name:"swipeDirection",type:"'up' | 'down' | 'left' | 'right' | 'start' | 'end'",defaultValue:"'end'",required:!1,description:"Drag direction to dismiss. start/end resolve against writing direction and mirror under RTL; the four physical values are absolute."},{name:"swipeThreshold",type:"number",defaultValue:"50",required:!1,description:'Pixels along swipeDirection to dismiss. Short of it, the drag snaps back (data-swipe="cancel").'}]},{name:"Toaster",aliases:[{name:"Region",frameworks:["webComponents"],frameworksReason:"A custom element is named by its tag, and <skdx-toaster> would read as the store rather than the landmark it renders; <skdx-toast-region> names the region a page mounts once."}],description:"The region component that renders at most `max` entries from the store into a ToastProvider + ToastViewport. React/Vue/Svelte/Web Components take the store as a `toaster` prop; Angular injects SkdxToastService instead. Takes no props: max/placement/duration/label are set on the store (see contextApi). Translates each entry's type onto data-type (queue's vocabulary) and data-status (severity vocabulary for styling)."},{name:"Viewport",description:'Fixed region the toasts stack in: role="region", labelled by Provider, tabindex="-1", data-placement for docking.',props:[{name:"hotkey",type:"string[]",defaultValue:"['F6']",required:!1,description:"Keys to move focus into the region and (with Shift) back out. Modifiers (Alt/Control/Meta/Shift) precede the KeyboardEvent.key. Appended to the region's accessible name so users know about it."}]},{name:"Title",description:"The toast's heading, published as aria-labelledby for keyboard entry."},{name:"Description",description:"The toast's body text, published as aria-describedby."},{name:"Action",description:'Optional actionable button ("Undo"). Runs the handler then closes the toast.',props:[{name:"altText",type:"string",required:!0,description:'What the action does (e.g., "Undo upload"). Required: the verb alone means nothing after the toast disappears.'}]},{name:"Close",description:'Dismissal button. Defaults to aria-label="Dismiss".'}],slots:[{name:"default",description:"Toast's body (Title, Description, Action, Close)."}],states:["open","closed","queued"],controlledUncontrolled:"Uncontrolled by default: starts open and self-closes. Controlled via open prop.",formBehavior:'Not applicable \u2014 Toast participates in no form; Action and Close are type="button".',validationBehavior:"Not applicable.",accessibility:{role:`none \u2014 the toast root is a generic element; announcement is the ToastProvider announcer's job, and a role="status"/"alert" here would duplicate it. The one carve-out: a standalone <skdx-toast> with no provider above it sets role="status" on itself, because with no announcer in the tree the host is the only channel left`,keyboard:[{key:"Escape",behavior:"Closes the toast."},{key:"Tab",behavior:"Reaches Action/Close while the toast is visible; pauses the auto-dismiss timer while any part of it is focused."},{key:"F6 (ToastViewport's hotkey)",behavior:"Moves focus into the ToastViewport region from anywhere on the page \u2014 the way in for a keyboard user, since a toast never steals focus. Configurable per region via the Viewport part's hotkey, and appended to the region's accessible name so it is announced on arrival."},{key:"Shift+F6 (Shift + ToastViewport's hotkey)",behavior:"Returns focus to its previous location."}],ariaAttributes:["ToastProvider renders two persistent visually-hidden aria-live regions (polite and assertive) that announce toasts",'ToastViewport is role="region", labelled by ToastProvider with hotkey appended ("Notifications (F6)"), tabindex="-1"',"Toast root carries no live-region role: its text reaches exactly one aria-live region (the announcer)","Toast root aria-labelledby points at Title, aria-describedby at Description (keyboard entry users benefit)",'ToastClose carries aria-label="Dismiss" by default',"ToastAction carries its altText as aria-label"],namingRequirement:'ToastClose must have an accessible name. Defaults to aria-label="Dismiss" so screen-reader users can dismiss it.',focusManagement:"Does not steal focus \u2014 announced via aria-live instead. F6 enters the region, Shift+F6 leaves. Focus restores if the focused toast is removed. Hover/focus/page-hidden pauses the timer. Swipe is addition to Close/Escape/timer (WCAG 2.2 SC 2.5.1, 2.5.7)."},styling:{cssParts:["viewport","root","title","description","action","close"],dataAttributes:[...t,"data-state","data-type","data-status","data-paused","data-swipe","data-swipe-direction","data-placement","data-skdx-toast-announcer"],cssCustomProperties:["--skdx-toast-swipe-move-x","--skdx-toast-swipe-move-y"]},publicMethods:[{name:"setOpen",signature:"(open: boolean) => void",frameworks:["webComponents"],exception:"imperative",frameworksReason:"Only the custom element publishes it. The other four re-render with a new open prop and never expose a setter; a Web Components consumer holds the element itself, so the same change is made by calling this method on it.",description:"Shows or dismisses this toast and emits open-change, exactly as its close control would."}],contextApi:[{name:"createToaster",signature:"(options?: { max?: number; placement?: ToastPlacement; duration?: number; label?: string }) => ToasterStore",description:"Creates the queue once per app. Options are read-only on the store; no component accepts them as props.",accessors:{react:"createToaster({ max, placement, duration, label }) from '@skdx/react'",vue:"createToaster({ max, placement, duration, label }) from '@skdx/vue'",svelte:"createToaster({ max, placement, duration, label }) from '@skdx/svelte'",angular:"{ provide: SKDX_TOASTER_OPTIONS, useValue: { max, placement, duration, label } } in the root providers",webComponents:"createToaster({ max, placement, duration, label }) from '@skdx/web-components', assigned to <skdx-toast-region>.toaster"}},{name:"max",signature:"readonly max: number",defaultValue:"3",description:"Visible toasts at once; rest queue in FIFO order. Set in createToaster/SKDX_TOASTER_OPTIONS.",accessors:{react:"createToaster().max",vue:"createToaster().max",svelte:"createToaster().max",angular:"inject(SkdxToastService).max",webComponents:"createToaster().max"}},{name:"placement",signature:"readonly placement: 'top-start' | 'top' | 'top-end' | 'bottom-start' | 'bottom' | 'bottom-end'",defaultValue:"'bottom-end'",description:"Region position, emitted as data-placement for layout. Set in createToaster/SKDX_TOASTER_OPTIONS.",accessors:{react:"createToaster().placement",vue:"createToaster().placement",svelte:"createToaster().placement",angular:"inject(SkdxToastService).placement",webComponents:"createToaster().placement"}},{name:"duration",signature:"readonly duration: number",defaultValue:"5000",description:"Default auto-dismiss duration (ms) for queued toasts, minimum 5000: WCAG 2.2.1. Loading toasts never auto-dismiss. Set in createToaster/SKDX_TOASTER_OPTIONS.",accessors:{react:"createToaster().duration",vue:"createToaster().duration",svelte:"createToaster().duration",angular:"inject(SkdxToastService).duration",webComponents:"createToaster().duration"}},{name:"label",signature:"readonly label: string",defaultValue:"'Notifications'",description:"Accessible label for the region landmark. Set in createToaster/SKDX_TOASTER_OPTIONS.",accessors:{react:"createToaster().label",vue:"createToaster().label",svelte:"createToaster().label",angular:"inject(SkdxToastService).label",webComponents:"createToaster().label"}},{name:"create",signature:"(options?: { id?, title?, description?, type?, duration?, action?, closeLabel? }) => string",description:"Queues a toast and returns its id. Creating with an existing id replaces that entry.",accessors:{react:"createToaster().create",vue:"createToaster().create",svelte:"createToaster().create",angular:"inject(SkdxToastService).create",webComponents:"createToaster().create"}},{name:"update",signature:"(id: string, options: ToastOptions) => void",description:"Merges options into a queued toast and re-announces it.",accessors:{react:"createToaster().update",vue:"createToaster().update",svelte:"createToaster().update",angular:"inject(SkdxToastService).update",webComponents:"createToaster().update"}},{name:"dismiss",signature:"(id?: string) => void",description:"Removes one toast or the entire queue.",accessors:{react:"createToaster().dismiss",vue:"createToaster().dismiss",svelte:"createToaster().dismiss",angular:"inject(SkdxToastService).dismiss",webComponents:"createToaster().dismiss"}},{name:"promise",signature:"<T>(promise: Promise<T>, messages: { loading, success, error }, options?: ToastOptions) => Promise<T>",description:"Shows a loading toast, then success or error when the promise settles. Rejections are re-thrown.",accessors:{react:"createToaster().promise",vue:"createToaster().promise",svelte:"createToaster().promise",angular:"inject(SkdxToastService).promise",webComponents:"createToaster().promise"}}],frameworkExtensions:[{framework:"webComponents",part:"Region",name:"toaster",type:"ToasterStore",description:`The queue to render. Defaults to a toaster of its own, so a region
2
+ dropped in with no wiring still works \u2014 read it back off the element to
3
+ raise toasts against it. This wrapper receives the toaster factory/context instance as an explicit property; Angular connects the same toast lifecycle through its provider/directive integration.`,kind:"prop"},{framework:"react",part:"Toaster",name:"toaster",type:"ToasterStore",description:"The queue to render, from createToaster. This wrapper receives the toaster factory/context instance as an explicit property; Angular connects the same toast lifecycle through its provider/directive integration.",kind:"prop"},{framework:"vue",part:"Toaster",name:"toaster",type:"ToasterStore",description:"The queue to render, from `createToaster`. This wrapper receives the toaster factory/context instance as an explicit property; Angular connects the same toast lifecycle through its provider/directive integration.",kind:"prop"},{framework:"svelte",part:"Toaster",name:"toaster",type:"ToasterStore",description:"The queue to render, from `createToaster`. This wrapper receives the toaster factory/context instance as an explicit property; Angular connects the same toast lifecycle through its provider/directive integration.",kind:"prop"},{framework:"webComponents",part:"Toaster",name:"toaster",type:"ToasterStore",description:`The queue to render. Defaults to a toaster of its own, so a region
4
+ dropped in with no wiring still works \u2014 read it back off the element to
5
+ raise toasts against it. This wrapper receives the toaster factory/context instance as an explicit property; Angular connects the same toast lifecycle through its provider/directive integration.`,kind:"prop"},{framework:"angular",part:"Viewport",name:"placement",type:"string",description:"Region placement, emitted as `data-placement` for styling. Angular places its rendered toast viewport through this directive input; other wrappers derive viewport placement from their toaster/context configuration.",kind:"prop"},{framework:"webComponents",part:"Region",name:"toaster",type:"ToasterStore",description:`The queue to render. Defaults to a toaster of its own, so a region
6
+ dropped in with no wiring still works \u2014 read it back off the element to
7
+ raise toasts against it. This wrapper receives the toaster factory/context instance as an explicit property; Angular connects the same toast lifecycle through its provider/directive integration.`},{framework:"react",part:"Toaster",name:"toaster",type:"ToasterStore",description:"The queue to render, from createToaster. This wrapper receives the toaster factory/context instance as an explicit property; Angular connects the same toast lifecycle through its provider/directive integration."},{framework:"vue",part:"Toaster",name:"toaster",type:"ToasterStore",description:"The queue to render, from `createToaster`. This wrapper receives the toaster factory/context instance as an explicit property; Angular connects the same toast lifecycle through its provider/directive integration."},{framework:"svelte",part:"Toaster",name:"toaster",type:"ToasterStore",description:"The queue to render, from `createToaster`. This wrapper receives the toaster factory/context instance as an explicit property; Angular connects the same toast lifecycle through its provider/directive integration."},{framework:"webComponents",part:"Toaster",name:"toaster",type:"ToasterStore",description:`The queue to render. Defaults to a toaster of its own, so a region
8
+ dropped in with no wiring still works \u2014 read it back off the element to
9
+ raise toasts against it. This wrapper receives the toaster factory/context instance as an explicit property; Angular connects the same toast lifecycle through its provider/directive integration.`},{framework:"angular",part:"Viewport",name:"placement",type:"string",description:"Region placement, emitted as `data-placement` for styling. Angular places its rendered toast viewport through this directive input; other wrappers derive viewport placement from their toaster/context configuration."}]};export{s as toastContract};
@@ -1 +1 @@
1
- import{changeDetail as e}from"./change-reason.js";import{SELECTION_ATTRIBUTE_RULE as t,SELECTION_INPUT_RULE as a,SELECTION_OUTPUT_RULE as o}from"./selection-value.js";import{ROOT_DATA_ATTRIBUTES as r}from"./styling-hooks.js";var l={name:"Toolbar",purpose:"A roving-tabindex group of controls (buttons, links, toggles) \u2014 one Tab stop, arrow keys move between items. Use for three or more; two buttons don't need it. Avoid nested components that own arrow keys (Tabs, Slider, Listbox, RadioGroup, Menu). ToggleGroup is safe (no key claims).",props:[{name:"orientation",type:"'horizontal' | 'vertical'",defaultValue:"'horizontal'",required:!1,description:"Layout axis for arrow-key roving-tabindex navigation."},{name:"loop",type:"boolean",defaultValue:"true",required:!1,description:"Whether arrow-key navigation wraps from the last item back to the first."},{name:"items",type:"ToolbarItem[]",required:!1,description:"Controls for the derived toolbar (Button, Link, Separator, or ToggleGroup), narrowed by optional `type` discriminant. Renders the row from data instead of hand-composed parts."},{name:"renderItem",type:"(item: ToolbarItem) => React.ReactNode",required:!1,frameworks:["react","svelte","angular"],exception:"render-prop",frameworksReason:'Vue expresses this as a scoped #item slot, Web Components as a light-DOM slot="item" child keyed by data-value \u2014 neither a declared prop an extractor can see, and Web Components falls back to the plain label when nothing matches. Angular ships the same name as a TemplateRef input.',description:"Customises what renders *inside* a derived control, never the control itself \u2014 the element's role, id, data attributes, roving tab stop and disabled state stay the library's; only read while `items` is deriving the anatomy."}],events:[{name:"value-change",description:`Fires from ToggleGroup (not the root) whenever an item is pressed or unpressed, controlled or not \u2014 declared here since the contract has one events array. ${o}`,detail:e("string[]","selectedValue: string | null")},{name:"value-change-detail",frameworks:["angular"],exception:"event-shape",frameworksReason:"Angular's `valueChange` emits the whole `string[]` selection so `[(value)]` round-trips it; this second output carries the detail object the other four hand to `value-change`.",description:"Fires with valueChange from ToggleGroup, carrying the pressed values plus the item that changed.",detail:e("string[]","selectedValue: string | null")}],parts:[{name:"Button",description:"A plain action button in the toolbar, carrying the roving tab stop like every other item.",props:[{name:"disabled",type:"boolean",defaultValue:"false",required:!1,description:"Disables the button and takes it out of the roving-tabindex ring, so arrow keys skip it and it never claims the single tab stop."}]},{name:"ToggleGroup",description:'Groups a set of ToggleItems and owns their pressed state, as a role="group" inside the toolbar; tab-stop management stays with the root.',props:[{name:"type",type:"'single' | 'multiple'",required:!0,description:"'single' keeps at most one item pressed at a time, 'multiple' allows any number; required in all five packages with no default (a union discriminant in React/Vue/Svelte, thrown when absent in Angular/Web Components) since a silent fallback would quietly unpress the user's choice."},{name:"value",type:"string | string[]",required:!1,description:`Pressed item value(s), for controlled usage. Pair with value-change. ${a} ${o} ${t}`},{name:"defaultValue",type:"string | string[]",required:!1,description:`Initially pressed item value(s), for uncontrolled usage. Same shape as \`value\`. ${a} ${t}`},{name:"disabled",type:"boolean",defaultValue:"false",required:!1,description:"Disables every ToggleItem in the group; an item is disabled when either this or its own `disabled` prop is set \u2014 OR-ed, never overridden."}]},{name:"ToggleItem",description:"One pressable item inside a ToggleGroup, exposing its pressed state through aria-pressed and data-state.",props:[{name:"value",type:"string",required:!0,description:"This item's value \u2014 what the group reports as pressed, and what it matches against value/defaultValue."},{name:"disabled",type:"boolean",defaultValue:"false",required:!1,description:"Disables this item alone, on top of the group's; every package renders it as a native button, so the `disabled` attribute carries the state, blocks activation, and is skipped by the roving-tabindex order."}]}],slots:[{name:"default",description:"Toolbar items \u2014 buttons, links, separators, toggle groups."}],states:["default"],controlledUncontrolled:"Stateless at the root \u2014 only the roving-tabindex current-stop is internal state. Its ToggleGroup splits the Angular pair: `valueChange` emits the whole `string[]` selection for `[(value)]`, `valueChangeDetail` carries the detail.",formBehavior:"Not applicable \u2014 Toolbar does not participate in forms.",validationBehavior:"Not applicable \u2014 Toolbar carries no value and participates in no validation.",accessibility:{role:"toolbar",keyboard:[{key:"ArrowLeft/ArrowRight (horizontal) or ArrowUp/ArrowDown (vertical)",behavior:"Moves focus between items."},{key:"Home",behavior:"Moves focus to the first enabled item \u2014 no state change, no event, just the roving tab stop."},{key:"End",behavior:"Moves focus to the last enabled item. No state change and no event."}],ariaAttributes:["aria-orientation reflects the orientation prop",'aria-label or aria-labelledby on the root, supplied by the consumer \u2014 role="toolbar" takes no name from its items, so an unnamed one announces as a bare "toolbar". No prop needed: passthrough already carries both.'],focusManagement:"Roving tabindex spans every focusable item, in two phases. Before hydration (SSR, an Astro island, an unhydrated RSC subtree) *every enabled item* is a tab stop, since the stop is resolved from a layout effect that hasn't run \u2014 an unreachable group is worse than a degraded one. From the first layout effect onward, exactly one item holds it; a disabled item never claims it, and the stop is re-seeded whenever the item holding it is removed."},styling:{cssParts:["root","button","link","separator","toggle-group","toggle-item"],dataAttributes:[...r,"data-orientation","data-disabled","data-skdx-toolbar-item","data-state","data-value",{name:"data-skdx-toolbar-generated",frameworks:["webComponents"],exception:"wc-marker",frameworksReason:'Marks a node the element generated for itself rather than one the consumer authored. React, Vue, Svelte and Angular re-render their derived anatomy declaratively, so "mine or theirs" is answered by the framework and no marker is needed.'}],cssCustomProperties:[]},publicMethods:[]};export{l as toolbarContract};
1
+ import{changeDetail as e}from"./change-reason.js";import{SELECTION_ATTRIBUTE_RULE as t,SELECTION_INPUT_RULE as a,SELECTION_OUTPUT_RULE as r}from"./selection-value.js";import{ROOT_DATA_ATTRIBUTES as o}from"./styling-hooks.js";var l={name:"Toolbar",purpose:"A roving-tabindex group of controls (buttons, links, toggles) \u2014 one Tab stop, arrow keys move between items. Use for three or more; two buttons don't need it. Avoid nested components that own arrow keys (Tabs, Slider, Listbox, RadioGroup, Menu). ToggleGroup is safe (no key claims).",props:[{name:"orientation",type:"'horizontal' | 'vertical'",defaultValue:"'horizontal'",required:!1,description:"Layout axis for arrow-key roving-tabindex navigation."},{name:"loop",type:"boolean",defaultValue:"true",required:!1,description:"Whether arrow-key navigation wraps from the last item back to the first."},{name:"items",type:"ToolbarItem[]",required:!1,description:"Controls for the derived toolbar (Button, Link, Separator, or ToggleGroup), narrowed by optional `type` discriminant. Renders the row from data instead of hand-composed parts."},{name:"renderItem",type:"(item: ToolbarItem) => React.ReactNode",required:!1,frameworks:["react","svelte","angular"],exception:"render-prop",frameworksReason:'Vue expresses this as a scoped #item slot, Web Components as a light-DOM slot="item" child keyed by data-value \u2014 neither a declared prop an extractor can see, and Web Components falls back to the plain label when nothing matches. Angular ships the same name as a TemplateRef input.',description:"Customises what renders *inside* a derived control, never the control itself \u2014 the element's role, id, data attributes, roving tab stop and disabled state stay the library's; only read while `items` is deriving the anatomy."}],events:[{name:"value-change",description:`Fires from ToggleGroup (not the root) whenever an item is pressed or unpressed, controlled or not \u2014 declared here since the contract has one events array. ${r}`,detail:e("string[]","selectedValue: string | null")},{name:"value-change-detail",frameworks:["angular"],exception:"event-shape",frameworksReason:"Angular's `valueChange` emits the whole `string[]` selection so `[(value)]` round-trips it; this second output carries the detail object the other four hand to `value-change`.",description:"Fires with valueChange from ToggleGroup, carrying the pressed values plus the item that changed.",detail:e("string[]","selectedValue: string | null")}],parts:[{name:"Button",description:"A plain action button in the toolbar, carrying the roving tab stop like every other item.",props:[{name:"disabled",type:"boolean",defaultValue:"false",required:!1,description:"Disables the button and takes it out of the roving-tabindex ring, so arrow keys skip it and it never claims the single tab stop."}]},{name:"ToggleGroup",description:'Groups a set of ToggleItems and owns their pressed state, as a role="group" inside the toolbar; tab-stop management stays with the root.',props:[{name:"type",type:"'single' | 'multiple'",required:!0,description:"'single' keeps at most one item pressed at a time, 'multiple' allows any number; required in all five packages with no default (a union discriminant in React/Vue/Svelte, thrown when absent in Angular/Web Components) since a silent fallback would quietly unpress the user's choice."},{name:"value",type:"string | string[]",required:!1,description:`Pressed item value(s), for controlled usage. Pair with value-change. ${a} ${r} ${t}`},{name:"defaultValue",type:"string | string[]",required:!1,description:`Initially pressed item value(s), for uncontrolled usage. Same shape as \`value\`. ${a} ${t}`},{name:"disabled",type:"boolean",defaultValue:"false",required:!1,description:"Disables every ToggleItem in the group; an item is disabled when either this or its own `disabled` prop is set \u2014 OR-ed, never overridden."}]},{name:"ToggleItem",description:"One pressable item inside a ToggleGroup, exposing its pressed state through aria-pressed and data-state.",props:[{name:"value",type:"string",required:!0,description:"This item's value \u2014 what the group reports as pressed, and what it matches against value/defaultValue."},{name:"disabled",type:"boolean",defaultValue:"false",required:!1,description:"Disables this item alone, on top of the group's; every package renders it as a native button, so the `disabled` attribute carries the state, blocks activation, and is skipped by the roving-tabindex order."}]}],slots:[{name:"default",description:"Toolbar items \u2014 buttons, links, separators, toggle groups."},{name:"item",frameworks:["vue","webComponents"],exception:"render-prop",frameworksReason:"vue, webComponents expose item as native content projection for the renderItem rendering capability. Other frameworks pass the documented renderItem render prop or template instead of a named slot with this spelling.",description:"Customises what renders *inside* a derived control, never the control itself \u2014 the element's role, id, data attributes, roving tab stop and disabled state stay the library's; only read while `items` is deriving the anatomy."},{name:"render-item",frameworks:["svelte"],exception:"render-prop",frameworksReason:"svelte expose render-item as native content projection for the renderItem rendering capability. Other frameworks pass the documented renderItem render prop or template instead of a named slot with this spelling.",description:"Customises what renders *inside* a derived control, never the control itself \u2014 the element's role, id, data attributes, roving tab stop and disabled state stay the library's; only read while `items` is deriving the anatomy."}],states:["default"],controlledUncontrolled:"Stateless at the root \u2014 only the roving-tabindex current-stop is internal state. Its ToggleGroup splits the Angular pair: `valueChange` emits the whole `string[]` selection for `[(value)]`, `valueChangeDetail` carries the detail.",formBehavior:"Not applicable \u2014 Toolbar does not participate in forms.",validationBehavior:"Not applicable \u2014 Toolbar carries no value and participates in no validation.",accessibility:{role:"toolbar",keyboard:[{key:"ArrowLeft/ArrowRight (horizontal) or ArrowUp/ArrowDown (vertical)",behavior:"Moves focus between items."},{key:"Home",behavior:"Moves focus to the first enabled item \u2014 no state change, no event, just the roving tab stop."},{key:"End",behavior:"Moves focus to the last enabled item. No state change and no event."}],ariaAttributes:["aria-orientation reflects the orientation prop",'aria-label or aria-labelledby on the root, supplied by the consumer \u2014 role="toolbar" takes no name from its items, so an unnamed one announces as a bare "toolbar". No prop needed: passthrough already carries both.'],focusManagement:"Roving tabindex spans every focusable item, in two phases. Before hydration (SSR, an Astro island, an unhydrated RSC subtree) *every enabled item* is a tab stop, since the stop is resolved from a layout effect that hasn't run \u2014 an unreachable group is worse than a degraded one. From the first layout effect onward, exactly one item holds it; a disabled item never claims it, and the stop is re-seeded whenever the item holding it is removed."},styling:{cssParts:["root","button","link","separator","toggle-group","toggle-item"],dataAttributes:[...o,"data-orientation","data-disabled","data-skdx-toolbar-item","data-state","data-value",{name:"data-skdx-toolbar-generated",frameworks:["webComponents"],exception:"wc-marker",frameworksReason:'Marks a node the element generated for itself rather than one the consumer authored. React, Vue, Svelte and Angular re-render their derived anatomy declaratively, so "mine or theirs" is answered by the framework and no marker is needed.'}],cssCustomProperties:[]},publicMethods:[],frameworkExtensions:[{framework:"angular",part:"Link",name:"href",type:"string",description:"Native <a> href binding on angular ToolbarLink. The navigation target URL this link opens when clicked. This wrapper declares the native binding explicitly; other wrappers inherit native-element attributes rather than duplicate each DOM attribute as a component prop.",kind:"prop"},{framework:"webComponents",part:"Link",name:"href",type:"string",description:"Native <a> href binding on webComponents ToolbarLink. URL to navigate to when the link is clicked or activated. This wrapper declares the native binding explicitly; other wrappers inherit native-element attributes rather than duplicate each DOM attribute as a component prop.",kind:"prop"},{framework:"vue",part:"Link",name:"rel",type:"string",description:'Native <a> rel binding on vue ToolbarLink. Explicit `rel`; empty string opts out of the auto `noopener noreferrer` guard for `target="_blank"`. This wrapper declares the native binding explicitly; other wrappers inherit native-element attributes rather than duplicate each DOM attribute as a component prop.',kind:"prop"},{framework:"vue",part:"Link",name:"target",type:"string",description:"Native <a> target binding on vue ToolbarLink. Native `target` attribute value, passed straight through to the element. This wrapper declares the native binding explicitly; other wrappers inherit native-element attributes rather than duplicate each DOM attribute as a component prop.",kind:"prop"}]};export{l as toolbarContract};
@@ -1 +1 @@
1
- import{arrowPart as t,autoFocusEvents as r,dismissalEvents as a,forceMountProp as e,openChangeCompleteEvent as o,popperContentProps as n,portalPart as i}from"./overlay-parts.js";import{PART_DATA_ATTRIBUTES as s}from"./styling-hooks.js";var p={name:"Tour",purpose:'Stepped walkthrough with spotlight and focus trap. Root resolves targets (polling for late mounts), tracks index, publishes geometry as CSS vars. Content (role="dialog") positioned via popper against target or centered. Parts: Trigger, Portal, Backdrop, Spotlight, Content, Arrow, Title, Description, ProgressText, NextTrigger, PrevTrigger, SkipTrigger, CloseTrigger.',props:[{name:"steps",type:'Array<{ id?: string; target?: string | Element; title?: string; description?: string; placement?: Side; align?: Align; type?: "tooltip" | "dialog" | "floating"; backdrop?: boolean }>',required:!0,description:"Ordered steps (required). Empty steps render centered, spotlighting nothing \u2014 the open/close pattern. Stepper with no target emit step-target-missing. Step may carry id (stable across list changes, reported with index), description (adds aria-describedby to Content), and backdrop: false (hides dimming)."},{name:"open",type:"boolean",required:!1,description:"Controlled running state. Pair with the open-change event."},{name:"defaultOpen",type:"boolean",defaultValue:"false",required:!1,description:"Whether the tour is running on mount, for uncontrolled usage. The tour waits for a trigger by default, so a first-visit tour is started deliberately rather than on every render."},{name:"step",type:"number",required:!1,description:"Controlled index of the active step. Pair with the step-change event."},{name:"defaultStep",type:"number",defaultValue:"0",required:!1,description:"Index the tour starts on, for uncontrolled usage \u2014 useful for resuming a tour the user abandoned."},{name:"preventInteraction",type:"boolean",defaultValue:"false",required:!1,description:'Whether to inert the page. When true, Content declares aria-modal="true" and siblings are marked inert. When false (default), the page stays readable and operable; Content declares no aria-modal because claiming modality over a clickable page contradicts assistive technology. Focus trap holds either way.'},{name:"closeOnEscape",type:"boolean",defaultValue:"true",required:!1,description:"Whether Escape ends the tour. Set false only when tour cannot be abandoned; then provide SkipTrigger or CloseTrigger inside Content."},{name:"closeOnInteractOutside",type:"boolean",defaultValue:"false",required:!1,description:"Whether pointer-down outside content skips the tour. Off by default; outside clicks usually target the UI being explained."},{name:"spotlightPadding",type:"number",defaultValue:"8",required:!1,description:"Pixels of breathing room added around the target on every side when the spotlight geometry is published, so the cutout clears the target's own focus ring instead of clipping it."},{name:"spotlightRadius",type:"number",defaultValue:"0",required:!1,description:"Corner radius of the spotlight cutout in pixels, published as --skdx-tour-radius. Clip-path polygons cannot round their own corners."},{name:"scrollIntoView",type:"boolean | ScrollIntoViewOptions",defaultValue:"{ block: 'center', inline: 'center' }",required:!1,description:"Whether and how to scroll target into view. true (default) centers it; false suppresses scroll; object retunes. behavior forced to auto when prefers-reduced-motion: reduce. Web Components attribute is scroll-into-view; property is scrollIntoViewOption (scrollIntoView is an HTMLElement method)."},{name:"targetTimeout",type:"number",defaultValue:"2000",required:!1,description:"Milliseconds to keep polling for a step's target before giving up on it. Covers targets that mount late \u2014 a lazy route, a panel that opens on the previous step's action. Long enough for a lazy chunk to land, short enough that a genuinely absent target does not strand the tour."},{name:"keyboardNavigation",type:"boolean",defaultValue:"false",required:!1,description:"Whether ArrowRight/Left advance/step back while Content has focus (mirrored under RTL). Off by default; arrows belong to the target element. Published as data-keyboard-navigation; ignored in input fields inside the panel."},{name:"getAnnouncement",type:"(step: TourStep, index: number, total: number) => string",required:!1,description:`Builds the string announced to screen readers on every step change. Override it to translate, or to include more of the step than its title. Defaults to the step's title (when set) followed by "Step {n} of {total}", in English.`},{name:"showProgress",type:"boolean",defaultValue:"true",required:!1,description:"Whether derived anatomy includes ProgressText. Ignored under hand composition."},{name:"showSkip",type:"boolean",defaultValue:"true",required:!1,description:"Whether derived anatomy includes SkipTrigger. Ignored under hand composition. CloseTrigger never gated."},{name:"spotlight",type:"boolean",defaultValue:"true",required:!1,description:"Whether derived anatomy includes Spotlight cutout. Ignored under hand composition."}],events:[{name:"open-change",detail:"boolean",description:"Fired with the new open state whenever the tour starts or ends, controlled or not."},o("tour"),{name:"step-change",description:"Fired on step change with zero-based index and step id (if declared). Not fired on completion/skip.",detail:"{ index: number; id?: string }"},{name:"complete",detail:"void",description:"Fired when the user advances past the last step. The tour closes either way; this is where the consumer records that the user has seen it."},{name:"skip",detail:"void",description:"Fired when the tour ends early \u2014 the skip trigger, the close trigger, or Escape."},{name:"step-target-missing",description:"Fired when a step's target never appeared within targetTimeout. The tour then skips forward rather than rendering an unanchored popup. One argument, always the detail object `{ index, step }` \u2014 never the index and the step as two positional arguments.",detail:"{ index: number; step: TourStep }"},...a("tour"),...r("tour")],parts:[{name:"Trigger",description:'The button that starts the tour. Carries aria-haspopup="dialog", aria-expanded, and aria-controls.'},i,{name:"Backdrop",description:"The dimming layer, reading --skdx-tour-x/y/width/height so cutouts are drawable in pure CSS (clip-path, box-shadow, or panels). Renders nothing when active step sets backdrop: false; Content publishes data-backdrop when kept.",props:[e]},{name:"Spotlight",description:"Highlight box publishing --skdx-tour-x/y/width/height. Renders nothing when active step has no target.",props:[e]},{name:"Content",description:'Step panel with role="dialog", aria-modal="true" only when preventInteraction is set. Labeled by Title, described by Description. Carries data-step/type/backdrop (when dimming), data-keyboard-navigation (when arrows bound). Anchored via popper (data-side/align); centered steps get data-centered. Stays mounted so focus returns on each step. Positioning props are defaults that step placement/align override.',props:[...n({sideOffset:"8"}),e]},t,{name:"Title",description:"The active step's accessible name, referenced by Content's aria-labelledby."},{name:"Description",description:"Step's accessible description (Content's aria-describedby)."},{name:"ProgressText",description:"Progress display (data-step, data-total selectable from CSS).",props:[{name:"format",type:"(index: number, total: number) => string",required:!1,description:'Builds progress string from zero-based index and total (default: "Step {index + 1} of {total}").'}]},{name:"Actions",description:"Container for navigation triggers (Prev, Next, Skip, Close). No role, no label \u2014 Content already names the dialog. Gives the design layer a layout hook."},{name:"NextTrigger",description:"Advances the tour (completes on last step). Carries data-last for label changes."},{name:"PrevTrigger",description:"Stepper the tour back. Disabled on first step.",props:[{name:"disabled",type:"boolean",defaultValue:"false",required:!1,description:"Disables the back button. Set automatically on the first step, where there is nowhere to step back to; an explicit true disables it on any step."}]},{name:"SkipTrigger",description:"Ends the tour early (same as Escape)."},{name:"CloseTrigger",description:"Dismiss affordance (same as SkipTrigger)."}],slots:[{name:"trigger",description:"Trigger's content \u2014 the button that starts the tour."},{name:"default",description:"Content's body \u2014 Title, Description, ProgressText, and the navigation triggers. It is the same markup for every step; only the data behind it changes."}],states:["content:closed","content:open","content:tooltip","content:dialog","content:floating","content:centered","content:backdrop","content:keyboard-navigation","next-trigger:last","prev-trigger:disabled"],controlledUncontrolled:"Two independent controlled/uncontrolled axes \u2014 open and step \u2014 each starting from its default* prop and switching to controlled when the matching prop is passed. The steps array itself is always supplied by the consumer.",formBehavior:'Not applicable \u2014 Tour participates in no form; every trigger is type="button".',validationBehavior:"Not applicable \u2014 Tour collects no value.",accessibility:{role:'dialog (Content; aria-modal="true" only while preventInteraction is set, since the page is otherwise still interactive)',keyboard:[{key:"Enter / Space (on Trigger)",behavior:"Starts the tour."},{key:"Tab / Shift+Tab",behavior:"Cycle focus within the panel; does not escape to the page."},{key:"Enter / Space (on a navigation trigger)",behavior:"Advance, step back, skip, or close per trigger."},{key:"Escape",behavior:"End the tour when closeOnEscape is set (default); cancelable via escape-key-down."},{key:"ArrowRight / ArrowLeft (on Content)",behavior:"Unbound by default (arrows belong to the target element). With keyboardNavigation set, advance/step back (swapped under RTL). Ignored in input fields inside the panel. ArrowRight on last step completes."}],ariaAttributes:['aria-haspopup="dialog", aria-expanded, aria-controls on Trigger',`role="dialog" on Content, matching Trigger's aria-controls`,'aria-modal="true" on Content only when preventInteraction is set; siblings marked inert',"aria-labelledby on Content \u2192 Title; aria-describedby \u2192 Description","aria-hidden on Backdrop and Spotlight (decorative)","Each step change announced politely through live announcer (getAnnouncement)"],focusManagement:"Focus trapped inside Content regardless of preventInteraction; Tab containment makes the panel findable. With closeOnEscape false, provide focusable SkipTrigger or CloseTrigger. Focus is returned to whatever was focused before the tour started when it completes or is skipped. Panel stays mounted, so focus is pulled back onto it on every step (announced as open-auto-focus). Trap declared before step-change move, recording restore target first. open-auto-focus / close-auto-focus events are cancelable (first lets consumers place focus; second suppresses return). per-step re-focus is announced the same way; consumers can veto to keep focus where placed. Close override matters more here than other overlays.",namingRequirement:"Content must be named by Title. Each navigation trigger needs a descriptive label."},styling:{cssParts:["trigger","backdrop","spotlight","content","arrow","title","description","progress-text","actions","next-trigger","prev-trigger","skip-trigger","close-trigger"],dataAttributes:[...s,"data-state","data-step","data-total","data-type","data-side","data-align","data-centered","data-backdrop","data-keyboard-navigation","data-last","data-disabled",{name:"data-skdx-tour-generated",frameworks:["webComponents"],exception:"wc-marker",frameworksReason:'Marks a node the element generated for itself rather than one the consumer authored. React, Vue, Svelte and Angular re-render their derived anatomy declaratively, so "mine or theirs" is answered by the framework and no marker is needed.'}],cssCustomProperties:["--skdx-tour-x","--skdx-tour-y","--skdx-tour-width","--skdx-tour-height","--skdx-tour-radius","--skdx-tour-available-width"]},publicMethods:[]};export{p as tourContract};
1
+ import{arrowPart as t,autoFocusEvents as r,dismissalEvents as o,forceMountProp as e,openChangeCompleteEvent as n,popperContentProps as a,portalPart as i}from"./overlay-parts.js";import{PART_DATA_ATTRIBUTES as s}from"./styling-hooks.js";var l={name:"Tour",purpose:'Stepped walkthrough with spotlight and focus trap. Root resolves targets (polling for late mounts), tracks index, publishes geometry as CSS vars. Content (role="dialog") positioned via popper against target or centered. Parts: Trigger, Portal, Backdrop, Spotlight, Content, Arrow, Title, Description, ProgressText, NextTrigger, PrevTrigger, SkipTrigger, CloseTrigger.',props:[{name:"steps",type:'Array<{ id?: string; target?: string | Element; title?: string; description?: string; placement?: Side; align?: Align; type?: "tooltip" | "dialog" | "floating"; backdrop?: boolean }>',required:!0,description:"Ordered steps (required). Empty steps render centered, spotlighting nothing \u2014 the open/close pattern. Stepper with no target emit step-target-missing. Step may carry id (stable across list changes, reported with index), description (adds aria-describedby to Content), and backdrop: false (hides dimming)."},{name:"open",type:"boolean",required:!1,description:"Controlled running state. Pair with the open-change event."},{name:"defaultOpen",type:"boolean",defaultValue:"false",required:!1,description:"Whether the tour is running on mount, for uncontrolled usage. The tour waits for a trigger by default, so a first-visit tour is started deliberately rather than on every render."},{name:"step",type:"number",required:!1,description:"Controlled index of the active step. Pair with the step-change event."},{name:"defaultStep",type:"number",defaultValue:"0",required:!1,description:"Index the tour starts on, for uncontrolled usage \u2014 useful for resuming a tour the user abandoned."},{name:"preventInteraction",type:"boolean",defaultValue:"false",required:!1,description:'Whether to inert the page. When true, Content declares aria-modal="true" and siblings are marked inert. When false (default), the page stays readable and operable; Content declares no aria-modal because claiming modality over a clickable page contradicts assistive technology. Focus trap holds either way.'},{name:"closeOnEscape",type:"boolean",defaultValue:"true",required:!1,description:"Whether Escape ends the tour. Set false only when tour cannot be abandoned; then provide SkipTrigger or CloseTrigger inside Content."},{name:"closeOnInteractOutside",type:"boolean",defaultValue:"false",required:!1,description:"Whether pointer-down outside content skips the tour. Off by default; outside clicks usually target the UI being explained."},{name:"spotlightPadding",type:"number",defaultValue:"8",required:!1,description:"Pixels of breathing room added around the target on every side when the spotlight geometry is published, so the cutout clears the target's own focus ring instead of clipping it."},{name:"spotlightRadius",type:"number",defaultValue:"0",required:!1,description:"Corner radius of the spotlight cutout in pixels, published as --skdx-tour-radius. Clip-path polygons cannot round their own corners."},{name:"scrollIntoView",type:"boolean | ScrollIntoViewOptions",defaultValue:"{ block: 'center', inline: 'center' }",required:!1,description:"Whether and how to scroll target into view. true (default) centers it; false suppresses scroll; object retunes. behavior forced to auto when prefers-reduced-motion: reduce. Web Components attribute is scroll-into-view; property is scrollIntoViewOption (scrollIntoView is an HTMLElement method)."},{name:"targetTimeout",type:"number",defaultValue:"2000",required:!1,description:"Milliseconds to keep polling for a step's target before giving up on it. Covers targets that mount late \u2014 a lazy route, a panel that opens on the previous step's action. Long enough for a lazy chunk to land, short enough that a genuinely absent target does not strand the tour."},{name:"keyboardNavigation",type:"boolean",defaultValue:"false",required:!1,description:"Whether ArrowRight/Left advance/step back while Content has focus (mirrored under RTL). Off by default; arrows belong to the target element. Published as data-keyboard-navigation; ignored in input fields inside the panel."},{name:"getAnnouncement",type:"(step: TourStep, index: number, total: number) => string",required:!1,description:`Builds the string announced to screen readers on every step change. Override it to translate, or to include more of the step than its title. Defaults to the step's title (when set) followed by "Step {n} of {total}", in English.`},{name:"showProgress",type:"boolean",defaultValue:"true",required:!1,description:"Whether derived anatomy includes ProgressText. Ignored under hand composition."},{name:"showSkip",type:"boolean",defaultValue:"true",required:!1,description:"Whether derived anatomy includes SkipTrigger. Ignored under hand composition. CloseTrigger never gated."},{name:"spotlight",type:"boolean",defaultValue:"true",required:!1,description:"Whether derived anatomy includes Spotlight cutout. Ignored under hand composition."}],events:[{name:"open-change",detail:"boolean",description:"Fired with the new open state whenever the tour starts or ends, controlled or not."},n("tour"),{name:"step-change",description:"Fired on step change with zero-based index and step id (if declared). Not fired on completion/skip.",detail:"{ index: number; id?: string }"},{name:"complete",detail:"void",description:"Fired when the user advances past the last step. The tour closes either way; this is where the consumer records that the user has seen it."},{name:"skip",detail:"void",description:"Fired when the tour ends early \u2014 the skip trigger, the close trigger, or Escape."},{name:"step-target-missing",description:"Fired when a step's target never appeared within targetTimeout. The tour then skips forward rather than rendering an unanchored popup. One argument, always the detail object `{ index, step }` \u2014 never the index and the step as two positional arguments.",detail:"{ index: number; step: TourStep }"},...o("tour"),...r("tour")],parts:[{name:"Trigger",description:'The button that starts the tour. Carries aria-haspopup="dialog", aria-expanded, and aria-controls.',slots:[{name:"default",description:"Trigger's content \u2014 the button that starts the tour."}]},i,{name:"Backdrop",description:"The dimming layer, reading --skdx-tour-x/y/width/height so cutouts are drawable in pure CSS (clip-path, box-shadow, or panels). Renders nothing when active step sets backdrop: false; Content publishes data-backdrop when kept.",props:[e]},{name:"Spotlight",description:"Highlight box publishing --skdx-tour-x/y/width/height. Renders nothing when active step has no target.",props:[e]},{name:"Content",description:'Step panel with role="dialog", aria-modal="true" only when preventInteraction is set. Labeled by Title, described by Description. Carries data-step/type/backdrop (when dimming), data-keyboard-navigation (when arrows bound). Anchored via popper (data-side/align); centered steps get data-centered. Stays mounted so focus returns on each step. Positioning props are defaults that step placement/align override.',props:[...a({sideOffset:"8"}),e]},t,{name:"Title",description:"The active step's accessible name, referenced by Content's aria-labelledby."},{name:"Description",description:"Step's accessible description (Content's aria-describedby)."},{name:"ProgressText",description:"Progress display (data-step, data-total selectable from CSS).",props:[{name:"format",type:"(index: number, total: number) => string",required:!1,description:'Builds progress string from zero-based index and total (default: "Step {index + 1} of {total}").'}]},{name:"Actions",description:"Container for navigation triggers (Prev, Next, Skip, Close). No role, no label \u2014 Content already names the dialog. Gives the design layer a layout hook."},{name:"NextTrigger",description:"Advances the tour (completes on last step). Carries data-last for label changes."},{name:"PrevTrigger",description:"Stepper the tour back. Disabled on first step.",props:[{name:"disabled",type:"boolean",defaultValue:"false",required:!1,description:"Disables the back button. Set automatically on the first step, where there is nowhere to step back to; an explicit true disables it on any step."}]},{name:"SkipTrigger",description:"Ends the tour early (same as Escape)."},{name:"CloseTrigger",description:"Dismiss affordance (same as SkipTrigger)."}],slots:[{name:"default",description:"Content's body \u2014 Title, Description, ProgressText, and the navigation triggers. It is the same markup for every step; only the data behind it changes."}],states:["content:closed","content:open","content:tooltip","content:dialog","content:floating","content:centered","content:backdrop","content:keyboard-navigation","next-trigger:last","prev-trigger:disabled"],controlledUncontrolled:"Two independent controlled/uncontrolled axes \u2014 open and step \u2014 each starting from its default* prop and switching to controlled when the matching prop is passed. The steps array itself is always supplied by the consumer.",formBehavior:'Not applicable \u2014 Tour participates in no form; every trigger is type="button".',validationBehavior:"Not applicable \u2014 Tour collects no value.",accessibility:{role:'dialog (Content; aria-modal="true" only while preventInteraction is set, since the page is otherwise still interactive)',keyboard:[{key:"Enter / Space (on Trigger)",behavior:"Starts the tour."},{key:"Tab / Shift+Tab",behavior:"Cycle focus within the panel; does not escape to the page."},{key:"Enter / Space (on a navigation trigger)",behavior:"Advance, step back, skip, or close per trigger."},{key:"Escape",behavior:"End the tour when closeOnEscape is set (default); cancelable via escape-key-down."},{key:"ArrowRight / ArrowLeft (on Content)",behavior:"Unbound by default (arrows belong to the target element). With keyboardNavigation set, advance/step back (swapped under RTL). Ignored in input fields inside the panel. ArrowRight on last step completes."}],ariaAttributes:['aria-haspopup="dialog", aria-expanded, aria-controls on Trigger',`role="dialog" on Content, matching Trigger's aria-controls`,'aria-modal="true" on Content only when preventInteraction is set; siblings marked inert',"aria-labelledby on Content \u2192 Title; aria-describedby \u2192 Description","aria-hidden on Backdrop and Spotlight (decorative)","Each step change announced politely through live announcer (getAnnouncement)"],focusManagement:"Focus trapped inside Content regardless of preventInteraction; Tab containment makes the panel findable. With closeOnEscape false, provide focusable SkipTrigger or CloseTrigger. Focus is returned to whatever was focused before the tour started when it completes or is skipped. Panel stays mounted, so focus is pulled back onto it on every step (announced as open-auto-focus). Trap declared before step-change move, recording restore target first. open-auto-focus / close-auto-focus events are cancelable (first lets consumers place focus; second suppresses return). per-step re-focus is announced the same way; consumers can veto to keep focus where placed. Close override matters more here than other overlays.",namingRequirement:"Content must be named by Title. Each navigation trigger needs a descriptive label."},styling:{cssParts:["trigger","backdrop","spotlight","content","arrow","title","description","progress-text","actions","next-trigger","prev-trigger","skip-trigger","close-trigger"],dataAttributes:[...s,"data-state","data-step","data-total","data-type","data-side","data-align","data-centered","data-backdrop","data-keyboard-navigation","data-last","data-disabled",{name:"data-skdx-tour-generated",frameworks:["webComponents"],exception:"wc-marker",frameworksReason:'Marks a node the element generated for itself rather than one the consumer authored. React, Vue, Svelte and Angular re-render their derived anatomy declaratively, so "mine or theirs" is answered by the framework and no marker is needed.'}],cssCustomProperties:["--skdx-tour-x","--skdx-tour-y","--skdx-tour-width","--skdx-tour-height","--skdx-tour-radius","--skdx-tour-available-width"]},publicMethods:[],frameworkExtensions:[{framework:"webComponents",name:"activeStep",type:"SkdxTourStep | undefined",description:"The active step or undefined. This documented getter exposes the native element\u2019s derived live state. Other wrappers expose their state through context or framework refs; the shared component contract does not require identical getter spellings.",kind:"prop"},{framework:"webComponents",name:"scrollIntoViewOption",type:"boolean | ScrollIntoViewOptions | undefined",description:"How to scroll the target into view before positioning.\nRespects `prefers-reduced-motion: reduce`.\nProperty is `scrollIntoViewOption` (not `scrollIntoView`,\nwhich is a native HTMLElement method). This is documented configuration/state on the native element or its composed part beyond the common component core. It is explicitly recorded as an element adapter rather than falsely requiring the same property binding on every wrapper.",kind:"prop"},{framework:"webComponents",name:"total",type:"number",description:"Number of steps in the tour. This documented getter exposes the native element\u2019s derived live state. Other wrappers expose their state through context or framework refs; the shared component contract does not require identical getter spellings.",kind:"prop"},{framework:"webComponents",name:"activeStep",type:"SkdxTourStep | undefined",description:"The active step or undefined. This documented getter exposes the native element\u2019s derived live state. Other wrappers expose their state through context or framework refs; the shared component contract does not require identical getter spellings."},{framework:"webComponents",name:"scrollIntoViewOption",type:"boolean | ScrollIntoViewOptions | undefined",description:"How to scroll the target into view before positioning.\nRespects `prefers-reduced-motion: reduce`.\nProperty is `scrollIntoViewOption` (not `scrollIntoView`,\nwhich is a native HTMLElement method). This is documented configuration/state on the native element or its composed part beyond the common component core. It is explicitly recorded as an element adapter rather than falsely requiring the same property binding on every wrapper."},{framework:"webComponents",name:"total",type:"number",description:"Number of steps in the tour. This documented getter exposes the native element\u2019s derived live state. Other wrappers expose their state through context or framework refs; the shared component contract does not require identical getter spellings."}]};export{l as tourContract};
@@ -1 +1 @@
1
- import{changeDetail as e}from"./change-reason.js";import{ROOT_DATA_ATTRIBUTES as t}from"./styling-hooks.js";var a={name:"Transfer",purpose:'A dual-listbox picker: two multi-select role="listbox" sides derived from one items collection plus the target-side id array, with move triggers between them, optional per-list search, and every move announced politely. Composed of a root plus List, ListHeader, ListSearch, Item, Controls, MoveTrigger, MoveAllTrigger, and Empty parts.',props:[{name:"items",type:"Array<{ value: string; label: string; disabled?: boolean }>",required:!0,description:"The whole collection. Each side is derived from this plus value, never stored separately, so the two lists cannot drift apart."},{name:"value",type:"string[]",required:!1,description:"Controlled ids on the target side. Pair with the value-change event. Rendering order follows items, not this array."},{name:"defaultValue",type:"string[]",required:!1,description:"Initial target-side ids for uncontrolled usage. Omit to start all items on the source side."},{name:"disabled",type:"boolean",defaultValue:"false",required:!1,description:"Disables both lists and all move triggers. Individual items are disabled through the collection."},{name:"name",type:"string",required:!1,description:"Form field name. When set, each target-side id submits as a separate entry (like `<select multiple>`), not a joined string. Omit to submit nothing."},{name:"form",type:"string",required:!1,description:"Form id for a Transfer outside its ancestor form. Mirrors the native form attribute."},{name:"searchable",type:"boolean",defaultValue:"false",required:!1,description:"Enables per-list filtering. When false, ListSearch renders nothing and filter is never called."},{name:"filter",type:"(item: TransferItemData, query: string) => boolean",required:!1,description:"Decides whether an item survives a list's query. Consulted only while searchable. Defaults to case-insensitive substring match on the label. Overrides filterMatchMode when set. Property-only: <skdx-transfer> has no attribute form."},{name:"filterMatchMode",type:"'startsWith' | 'contains' | 'endsWith'",defaultValue:"'contains'",required:!1,description:"How the built-in matcher compares queries to item labels. Case-insensitive and trimmed. Ignored while a custom filter is set."},{name:"getMoveLabel",type:"(count: number, direction: TransferDirection, all: boolean) => string",required:!1,description:'Words the accessible name of each MoveTrigger and MoveAllTrigger. The all flag distinguishes between single and multi-item triggers, and count shows items that would move (zero included, so "why disabled" is clear). Computed from live counts, so only a function can express this \u2014 a static aria-label cannot. Defaults to English, e.g., "Move 3 items to selected". Property-only, no attribute form.'},{name:"getMoveAnnouncement",type:"(detail: { moved: number; remaining: number; direction: TransferDirection; from: TransferSide; to: TransferSide }) => string",required:!1,description:'Words the polite live-region announcement after each move. Return empty string to announce nothing. Defaults to English, e.g., "3 items moved to selected. 5 remaining in available." Property-only.'},{name:"getSearchResultsAnnouncement",type:"(detail: { shown: number; total: number; side: TransferSide; query: string }) => string",required:!1,description:`Words the polite live-region announcement when a list's search query settles. Return empty string to announce nothing. Fired per side, searchable only, never for empty query. Defaults to English, e.g., "4 of 20 items shown in available." Property-only.`},{name:"targetOrder",type:"'items' | 'append' | 'prepend'",defaultValue:"'items'",required:!1,description:"Where an item lands on the target side when moved. 'items' keeps the collection order on both sides. 'append' / 'prepend' place new items at the end or start, following the value array's order, so users see what they added."},{name:"titles",type:"{ source?: string; target?: string }",defaultValue:"{ source: 'Available items', target: 'Selected items' }",required:!1,description:"Headings for the two derived ListHeaders. Ignored with hand-composed children."},{name:"emptyText",type:"{ empty?: string; noMatch?: string }",defaultValue:`{ empty: 'Nothing here.', noMatch: 'No match for "{query}".' }`,required:!1,description:"Wording for the two derived Empty states: empty when the side holds no items, noMatch when search filtered all out. The {query} literal is replaced with the search text. Ignored with hand-composed children."},{name:"showMoveAll",type:"boolean",defaultValue:"false",required:!1,description:"Whether the derived Controls include a MoveAllTrigger per direction. Off by default, since bulk moves are destructive. Ignored with hand-composed children."},{name:"renderItem",type:"(item: TransferItemData) => React.ReactNode",required:!1,description:"Renders each derived Item's content in place of its label. Forwarded to each derived List. Ignored with hand-composed children."},{name:"density",type:"'compact' | (string & {})",required:!1,description:"Vertical rhythm rung forwarded to both Lists' data-density; either attribute may be written directly instead."},{name:"shape",type:"'square' | 'rounded' | (string & {})",required:!1,description:"Corner rounding rung forwarded to both Lists' data-shape; either attribute may be written directly instead."},{name:"size",type:"'sm' | 'lg' | (string & {})",required:!1,description:"Size rung forwarded to both Lists' data-size; either attribute may be written directly instead."}],events:[{name:"value-change",description:"Fired with the whole new target-side id array whenever a move happens, controlled or not.",detail:e("string[]","selectedValue: string | null")},{name:"move",description:"Fired alongside value-change with the items that moved and which way. For side effects that need the moved rows, not just new membership. The handler takes one argument, always the detail object `{ items, direction }` \u2014 never the two as separate positional arguments.",detail:"{ items: TransferItemData[]; direction: 'toTarget' | 'toSource' }"},{name:"list-scroll",description:"Fired when a list is scrolled, carrying which side and scroll geometry, so consumers can load next pages. Named list-scroll, not scroll, to avoid shadowing the native scroll event. Virtualization is not offered: headless components cannot know item height.",detail:"{ side: TransferSide; scrollTop: number; scrollHeight: number; clientHeight: number }"}],parts:[{name:"List",description:'One side of the widget: a multi-select role="listbox" with roving tab stop, typeahead, and per-side marked-for-move selection. Renders one Item per visible item.',props:[{name:"side",type:"'source' | 'target'",required:!0,description:"Which of the two sides this list renders the items for."},{name:"renderItem",type:"(item: TransferItemData) => React.ReactNode",required:!1,description:"Renders one item's content, in place of its label. The part that actually does the per-item rendering \u2014 the root's own renderItem is only forwarded here when the anatomy is derived."},{name:"density",type:"'compact' | (string & {})",required:!1,description:"Vertical rhythm rung, written to data-density on the list; the data-density attribute may be set directly instead."},{name:"shape",type:"'square' | 'rounded' | (string & {})",required:!1,description:"Corner rounding rung, written to data-shape on the list; the data-shape attribute may be set directly instead."},{name:"size",type:"'sm' | 'lg' | (string & {})",required:!1,description:"Visual size rung, written to data-size on the list; the data-size attribute may be set directly instead."}]},{name:"ListHeader",description:`The visible heading naming one list, pointed to by aria-labelledby. Switches its list from fallback name to this heading's text. Sits beside its List (role="listbox" may only contain options), and takes its own side prop.`,props:[{name:"side",type:"'source' | 'target'",required:!0,description:"Which side this header names. Must match the side of the list it sits beside."}]},{name:"ListSearch",lightDom:!0,description:"The filter input for one list. Renders nothing while the root is not searchable.",props:[{name:"side",type:"'source' | 'target'",required:!0,description:"Which of the two sides this input filters the items of."}]},{name:"Item",description:'One option (role="option") in a list. Click, Enter, or Space toggles it into the marked set. Shift+Click marks the range from the anchor, mirroring Shift+Arrow keys.',props:[{name:"value",type:"string",required:!0,description:"The id of the collection entry this option renders."},{name:"disabled",type:"boolean",defaultValue:"false",required:!1,description:"Excludes this option from marking, navigation, and moves."},{name:"textValue",type:"string",required:!1,description:"Text typeahead matches this option against. Falls back to the option's rendered text content."}]},{name:"Controls",description:'The named role="group" holding the move triggers. Announced as the thing that moves items, not loose controls between lists.'},{name:"MoveTrigger",description:"Moves the marked items one way. Carries data-count with the count. Is aria-disabled when nothing is marked on its origin side \u2014 never natively disabled, so Tab keeps it in the tab order. Its accessible name names the direction and count so nothing-to-move is explained, not mysterious.",props:[{name:"direction",type:"'toTarget' | 'toSource'",required:!0,description:"Which way this trigger moves: 'toTarget' adds to the value, 'toSource' removes from it."}]},{name:"MoveAllTrigger",description:"Moves every visible item one way \u2014 visible, not every item, so a move-all under an active search moves what the user can see.",props:[{name:"direction",type:"'toTarget' | 'toSource'",required:!0,description:"Which way this trigger moves: 'toTarget' adds to the value, 'toSource' removes from it."}]},{name:"Empty",description:"Shown while one side has nothing to render. Covers two states: empty (no items) and no-results (search filtered all). Publishes data-reason and data-query in all five ports, and additionally passes them to content: function child in React, default-slot props in Vue/Svelte, contentTemplate TemplateRef in Angular, named slots in Web Components.",props:[{name:"side",type:"'source' | 'target'",required:!0,description:"Which side's emptiness this message covers."}]}],slots:[{name:"default",description:"The widget's body \u2014 two Lists (each optionally with a ListHeader, a ListSearch, and an Empty) with a Controls group of move triggers between them."}],states:["selected","empty","disabled"],controlledUncontrolled:"Uncontrolled by default: starts from defaultValue and tracks target membership internally. Passing value switches to controlled. Marked-for-move selection and search queries are always internal, transient state.",formBehavior:"With a name, each target-side id submits as a separate entry (like `<select multiple>`), not a joined string. An empty target side submits nothing. React/Vue/Svelte/Angular render one visually-hidden native control per id; <skdx-transfer> uses ElementInternals.setFormValue. Without a name, submits nothing.",validationBehavior:'None: no required flag. Validation rules like "pick at least one" belong to the surrounding Field/Form.',accessibility:{role:'listbox (List, aria-multiselectable="true"), option (Item), group (Controls)',keyboard:[{key:"ArrowUp / ArrowDown",behavior:"Move the roving tab stop between options within one list."},{key:"Home / End",behavior:"Jump to the first/last option in the list."},{key:"Enter / Space",behavior:"Toggles the focused option into or out of the marked set, and sets the range anchor."},{key:"Shift + ArrowUp / ArrowDown",behavior:"Extends the marked range from the anchor \u2014 the last option marked without Shift \u2014 which is the keyboard equivalent of Shift+Click."},{key:"ArrowRight (source) / ArrowLeft (target)",behavior:"Moves the marked options out of this list, or the focused option alone if nothing is marked. Focus follows to the vacated position. Direction-aware under dir: the key reverses under RTL. This is the widget's fastest keyboard path."},{key:"Ctrl/Cmd + A",behavior:"Marks every enabled option currently visible in the focused list \u2014 and clears them all instead when every one is already marked, which is the only keyboard way back out of a large marked set."},{key:"Typing a letter",behavior:"Jumps to the next option in that list matching the typed text."},{key:"Tab",behavior:"Moves between lists, search inputs, and triggers. Each list is one tab stop."}],ariaAttributes:['role="listbox" with aria-multiselectable="true" on each List',"aria-labelledby on a List pointing at its ListHeader, fallback to aria-label","aria-selected on each Item reflecting marked-for-move state","aria-disabled on disabled options, skipped by navigation and dropped from moves","aria-controls on each ListSearch pointing at the list it filters",'role="group" with an accessible name on Controls',"Moves are announced politely with direction and count","Settled search queries are announced with visible and total counts","data-reason on Empty region distinguishes empty from filtered-to-nothing"],focusManagement:"Roving DOM focus. Each list is a single tab stop, so Tab crosses the widget in few presses. After a move, focus lands on the list the items left, at the vacated position. Focus never stays on a now-disabled trigger, and never falls to <body>.",namingRequirement:'Each List must be named, by a ListHeader or explicit aria-label. MoveTriggers must use logical names ("Add to selected"), never a direction ("Move right") that reverses under RTL.'},styling:{cssParts:["list-header","item","controls","empty","root","list-search","move-trigger","move-all-trigger"],dataAttributes:[...t,"data-side","data-direction","data-selected","data-disabled","data-empty","data-reason","data-query","data-count","data-value","data-text-value","data-multiple","data-state","data-density","data-shape","data-size",{name:"data-skdx-transfer-generated",frameworks:["webComponents"],exception:"wc-marker",frameworksReason:'Marks a node the element generated for itself rather than one the consumer authored. React, Vue, Svelte and Angular re-render their derived anatomy declaratively, so "mine or theirs" is answered by the framework and no marker is needed.'}],cssCustomProperties:[]},publicMethods:[{name:"checkValidity",signature:"(): boolean",description:"Returns whether the current selection satisfies the constraints, firing an invalid event if not. Same contract as the native method.",frameworks:["webComponents"],exception:"imperative",frameworksReason:"Only the custom element is itself a form-associated control; React, Vue, Svelte and Angular submit through a hidden input the consumer owns and validate it directly."},{name:"reportValidity",signature:"(): boolean",description:"Same as checkValidity, but also shows the browser's validation message when the selection is invalid.",frameworks:["webComponents"],exception:"imperative",frameworksReason:"Same as checkValidity."},{name:"setCustomValidity",signature:"(message: string): void",description:"Sets an externally-computed error on the element, exactly like the native method: a non-empty message makes it invalid and is what reportValidity() shows; '' clears it.",frameworks:["webComponents"],exception:"imperative",frameworksReason:"Same as checkValidity."}]};export{a as transferContract};
1
+ import{changeDetail as e}from"./change-reason.js";import{ROOT_DATA_ATTRIBUTES as t}from"./styling-hooks.js";var r={name:"Transfer",purpose:'A dual-listbox picker: two multi-select role="listbox" sides derived from one items collection plus the target-side id array, with move triggers between them, optional per-list search, and every move announced politely. Composed of a root plus List, ListHeader, ListSearch, Item, Controls, MoveTrigger, MoveAllTrigger, and Empty parts.',props:[{name:"items",type:"Array<{ value: string; label: string; disabled?: boolean }>",required:!0,description:"The whole collection. Each side is derived from this plus value, never stored separately, so the two lists cannot drift apart."},{name:"value",type:"string[]",required:!1,description:"Controlled ids on the target side. Pair with the value-change event. Rendering order follows items, not this array."},{name:"defaultValue",type:"string[]",required:!1,description:"Initial target-side ids for uncontrolled usage. Omit to start all items on the source side."},{name:"disabled",type:"boolean",defaultValue:"false",required:!1,description:"Disables both lists and all move triggers. Individual items are disabled through the collection."},{name:"name",type:"string",required:!1,description:"Form field name. When set, each target-side id submits as a separate entry (like `<select multiple>`), not a joined string. Omit to submit nothing."},{name:"form",type:"string",required:!1,description:"Form id for a Transfer outside its ancestor form. Mirrors the native form attribute."},{name:"searchable",type:"boolean",defaultValue:"false",required:!1,description:"Enables per-list filtering. When false, ListSearch renders nothing and filter is never called."},{name:"filter",type:"(item: TransferItemData, query: string) => boolean",required:!1,description:"Decides whether an item survives a list's query. Consulted only while searchable. Defaults to case-insensitive substring match on the label. Overrides filterMatchMode when set. Property-only: <skdx-transfer> has no attribute form."},{name:"filterMatchMode",type:"'startsWith' | 'contains' | 'endsWith'",defaultValue:"'contains'",required:!1,description:"How the built-in matcher compares queries to item labels. Case-insensitive and trimmed. Ignored while a custom filter is set."},{name:"getMoveLabel",type:"(count: number, direction: TransferDirection, all: boolean) => string",required:!1,description:'Words the accessible name of each MoveTrigger and MoveAllTrigger. The all flag distinguishes between single and multi-item triggers, and count shows items that would move (zero included, so "why disabled" is clear). Computed from live counts, so only a function can express this \u2014 a static aria-label cannot. Defaults to English, e.g., "Move 3 items to selected". Property-only, no attribute form.'},{name:"getMoveAnnouncement",type:"(detail: { moved: number; remaining: number; direction: TransferDirection; from: TransferSide; to: TransferSide }) => string",required:!1,description:'Words the polite live-region announcement after each move. Return empty string to announce nothing. Defaults to English, e.g., "3 items moved to selected. 5 remaining in available." Property-only.'},{name:"getSearchResultsAnnouncement",type:"(detail: { shown: number; total: number; side: TransferSide; query: string }) => string",required:!1,description:`Words the polite live-region announcement when a list's search query settles. Return empty string to announce nothing. Fired per side, searchable only, never for empty query. Defaults to English, e.g., "4 of 20 items shown in available." Property-only.`},{name:"targetOrder",type:"'items' | 'append' | 'prepend'",defaultValue:"'items'",required:!1,description:"Where an item lands on the target side when moved. 'items' keeps the collection order on both sides. 'append' / 'prepend' place new items at the end or start, following the value array's order, so users see what they added."},{name:"titles",type:"{ source?: string; target?: string }",defaultValue:"{ source: 'Available items', target: 'Selected items' }",required:!1,description:"Headings for the two derived ListHeaders. Ignored with hand-composed children."},{name:"emptyText",type:"{ empty?: string; noMatch?: string }",defaultValue:`{ empty: 'Nothing here.', noMatch: 'No match for "{query}".' }`,required:!1,description:"Wording for the two derived Empty states: empty when the side holds no items, noMatch when search filtered all out. The {query} literal is replaced with the search text. Ignored with hand-composed children."},{name:"showMoveAll",type:"boolean",defaultValue:"false",required:!1,description:"Whether the derived Controls include a MoveAllTrigger per direction. Off by default, since bulk moves are destructive. Ignored with hand-composed children."},{name:"renderItem",type:"(item: TransferItemData) => React.ReactNode",required:!1,description:"Renders each derived Item's content in place of its label. Forwarded to each derived List. Ignored with hand-composed children."},{name:"density",type:"'compact' | (string & {})",required:!1,description:"Vertical rhythm rung forwarded to both Lists' data-density; either attribute may be written directly instead."},{name:"shape",type:"'square' | 'rounded' | (string & {})",required:!1,description:"Corner rounding rung forwarded to both Lists' data-shape; either attribute may be written directly instead."},{name:"size",type:"'sm' | 'lg' | (string & {})",required:!1,description:"Size rung forwarded to both Lists' data-size; either attribute may be written directly instead."}],events:[{name:"value-change",description:"Fired with the whole new target-side id array whenever a move happens, controlled or not.",detail:e("string[]","selectedValue: string | null")},{name:"move",description:"Fired alongside value-change with the items that moved and which way. For side effects that need the moved rows, not just new membership. The handler takes one argument, always the detail object `{ items, direction }` \u2014 never the two as separate positional arguments.",detail:"{ items: TransferItemData[]; direction: 'toTarget' | 'toSource' }"},{name:"list-scroll",description:"Fired when a list is scrolled, carrying which side and scroll geometry, so consumers can load next pages. Named list-scroll, not scroll, to avoid shadowing the native scroll event. Virtualization is not offered: headless components cannot know item height.",detail:"{ side: TransferSide; scrollTop: number; scrollHeight: number; clientHeight: number }"}],parts:[{name:"List",description:'One side of the widget: a multi-select role="listbox" with roving tab stop, typeahead, and per-side marked-for-move selection. Renders one Item per visible item.',props:[{name:"side",type:"'source' | 'target'",required:!0,description:"Which of the two sides this list renders the items for."},{name:"renderItem",type:"(item: TransferItemData) => React.ReactNode",required:!1,description:"Renders one item's content, in place of its label. The part that actually does the per-item rendering \u2014 the root's own renderItem is only forwarded here when the anatomy is derived."},{name:"density",type:"'compact' | (string & {})",required:!1,description:"Vertical rhythm rung, written to data-density on the list; the data-density attribute may be set directly instead."},{name:"shape",type:"'square' | 'rounded' | (string & {})",required:!1,description:"Corner rounding rung, written to data-shape on the list; the data-shape attribute may be set directly instead."},{name:"size",type:"'sm' | 'lg' | (string & {})",required:!1,description:"Visual size rung, written to data-size on the list; the data-size attribute may be set directly instead."}]},{name:"ListHeader",description:`The visible heading naming one list, pointed to by aria-labelledby. Switches its list from fallback name to this heading's text. Sits beside its List (role="listbox" may only contain options), and takes its own side prop.`,props:[{name:"side",type:"'source' | 'target'",required:!0,description:"Which side this header names. Must match the side of the list it sits beside."}]},{name:"ListSearch",lightDom:!0,description:"The filter input for one list. Renders nothing while the root is not searchable.",props:[{name:"side",type:"'source' | 'target'",required:!0,description:"Which of the two sides this input filters the items of."}]},{name:"Item",description:'One option (role="option") in a list. Click, Enter, or Space toggles it into the marked set. Shift+Click marks the range from the anchor, mirroring Shift+Arrow keys.',props:[{name:"value",type:"string",required:!0,description:"The id of the collection entry this option renders."},{name:"disabled",type:"boolean",defaultValue:"false",required:!1,description:"Excludes this option from marking, navigation, and moves."},{name:"textValue",type:"string",required:!1,description:"Text typeahead matches this option against. Falls back to the option's rendered text content."}]},{name:"Controls",description:'The named role="group" holding the move triggers. Announced as the thing that moves items, not loose controls between lists.'},{name:"MoveTrigger",description:"Moves the marked items one way. Carries data-count with the count. Is aria-disabled when nothing is marked on its origin side \u2014 never natively disabled, so Tab keeps it in the tab order. Its accessible name names the direction and count so nothing-to-move is explained, not mysterious.",props:[{name:"direction",type:"'toTarget' | 'toSource'",required:!0,description:"Which way this trigger moves: 'toTarget' adds to the value, 'toSource' removes from it."}]},{name:"MoveAllTrigger",description:"Moves every visible item one way \u2014 visible, not every item, so a move-all under an active search moves what the user can see.",props:[{name:"direction",type:"'toTarget' | 'toSource'",required:!0,description:"Which way this trigger moves: 'toTarget' adds to the value, 'toSource' removes from it."}]},{name:"Empty",description:"Shown while one side has nothing to render. Covers two states: empty (no items) and no-results (search filtered all). Publishes data-reason and data-query in all five ports, and additionally passes them to content: function child in React, default-slot props in Vue/Svelte, contentTemplate TemplateRef in Angular, named slots in Web Components.",props:[{name:"side",type:"'source' | 'target'",required:!0,description:"Which side's emptiness this message covers."}]}],slots:[{name:"default",description:"The widget's body \u2014 two Lists (each optionally with a ListHeader, a ListSearch, and an Empty) with a Controls group of move triggers between them."},{name:"render-item",frameworks:["svelte"],exception:"render-prop",frameworksReason:"svelte expose render-item as native content projection for the renderItem rendering capability. Other frameworks pass the documented renderItem render prop or template instead of a named slot with this spelling.",description:"Renders each derived Item's content in place of its label. Forwarded to each derived List. Ignored with hand-composed children."}],states:["selected","empty","disabled"],controlledUncontrolled:"Uncontrolled by default: starts from defaultValue and tracks target membership internally. Passing value switches to controlled. Marked-for-move selection and search queries are always internal, transient state.",formBehavior:"With a name, each target-side id submits as a separate entry (like `<select multiple>`), not a joined string. An empty target side submits nothing. React/Vue/Svelte/Angular render one visually-hidden native control per id; <skdx-transfer> uses ElementInternals.setFormValue. Without a name, submits nothing.",validationBehavior:'None: no required flag. Validation rules like "pick at least one" belong to the surrounding Field/Form.',accessibility:{role:'listbox (List, aria-multiselectable="true"), option (Item), group (Controls)',keyboard:[{key:"ArrowUp / ArrowDown",behavior:"Move the roving tab stop between options within one list."},{key:"Home / End",behavior:"Jump to the first/last option in the list."},{key:"Enter / Space",behavior:"Toggles the focused option into or out of the marked set, and sets the range anchor."},{key:"Shift + ArrowUp / ArrowDown",behavior:"Extends the marked range from the anchor \u2014 the last option marked without Shift \u2014 which is the keyboard equivalent of Shift+Click."},{key:"ArrowRight (source) / ArrowLeft (target)",behavior:"Moves the marked options out of this list, or the focused option alone if nothing is marked. Focus follows to the vacated position. Direction-aware under dir: the key reverses under RTL. This is the widget's fastest keyboard path."},{key:"Ctrl/Cmd + A",behavior:"Marks every enabled option currently visible in the focused list \u2014 and clears them all instead when every one is already marked, which is the only keyboard way back out of a large marked set."},{key:"Typing a letter",behavior:"Jumps to the next option in that list matching the typed text."},{key:"Tab",behavior:"Moves between lists, search inputs, and triggers. Each list is one tab stop."}],ariaAttributes:['role="listbox" with aria-multiselectable="true" on each List',"aria-labelledby on a List pointing at its ListHeader, fallback to aria-label","aria-selected on each Item reflecting marked-for-move state","aria-disabled on disabled options, skipped by navigation and dropped from moves","aria-controls on each ListSearch pointing at the list it filters",'role="group" with an accessible name on Controls',"Moves are announced politely with direction and count","Settled search queries are announced with visible and total counts","data-reason on Empty region distinguishes empty from filtered-to-nothing"],focusManagement:"Roving DOM focus. Each list is a single tab stop, so Tab crosses the widget in few presses. After a move, focus lands on the list the items left, at the vacated position. Focus never stays on a now-disabled trigger, and never falls to <body>.",namingRequirement:'Each List must be named, by a ListHeader or explicit aria-label. MoveTriggers must use logical names ("Add to selected"), never a direction ("Move right") that reverses under RTL.'},styling:{cssParts:["list-header","item","controls","empty","root","list-search","move-trigger","move-all-trigger"],dataAttributes:[...t,"data-side","data-direction","data-selected","data-disabled","data-empty","data-reason","data-query","data-count","data-value","data-text-value","data-multiple","data-state","data-density","data-shape","data-size",{name:"data-skdx-transfer-generated",frameworks:["webComponents"],exception:"wc-marker",frameworksReason:'Marks a node the element generated for itself rather than one the consumer authored. React, Vue, Svelte and Angular re-render their derived anatomy declaratively, so "mine or theirs" is answered by the framework and no marker is needed.'}],cssCustomProperties:[]},publicMethods:[{name:"checkValidity",signature:"(): boolean",description:"Returns whether the current selection satisfies the constraints, firing an invalid event if not. Same contract as the native method.",frameworks:["webComponents"],exception:"imperative",frameworksReason:"Only the custom element is itself a form-associated control; React, Vue, Svelte and Angular submit through a hidden input the consumer owns and validate it directly."},{name:"reportValidity",signature:"(): boolean",description:"Same as checkValidity, but also shows the browser's validation message when the selection is invalid.",frameworks:["webComponents"],exception:"imperative",frameworksReason:"Same as checkValidity."},{name:"setCustomValidity",signature:"(message: string): void",description:"Sets an externally-computed error on the element, exactly like the native method: a non-empty message makes it invalid and is what reportValidity() shows; '' clears it.",frameworks:["webComponents"],exception:"imperative",frameworksReason:"Same as checkValidity."}],frameworkExtensions:[{framework:"angular",part:"ListSearch",name:"placeholder",type:"string",description:"Native <input> placeholder binding on angular TransferListSearch. Native `<input>` `placeholder`, applied directly since Angular's host attributes land on the tag, not the inner input. This wrapper declares the native binding explicitly; other wrappers inherit native-element attributes rather than duplicate each DOM attribute as a component prop.",kind:"prop"},{framework:"webComponents",part:"ListSearch",name:"placeholder",type:"string | undefined",description:"Native <input> placeholder binding on webComponents TransferListSearch. Placeholder text for the filter field. This wrapper declares the native binding explicitly; other wrappers inherit native-element attributes rather than duplicate each DOM attribute as a component prop.",kind:"prop"},{framework:"angular",part:"Empty",name:"contentTemplate",type:"TemplateRef<SkdxTransferEmptyContext>",description:'Template wording the message, receiving an SkdxTransferEmptyContext so "empty" and "no match" can read differently. This wrapper receives projected content as a render prop, Snippet or TemplateRef. Other wrappers supply the matching named slot or default content of the composed part; the property is framework rendering syntax.',kind:"prop"},{framework:"webComponents",part:"Empty",kind:"slot",name:"empty",type:"Content projection",description:"The message shown while the side genuinely holds nothing. This framework spells the content customization as a native named slot/snippet on this part; consumers of other wrappers compose the corresponding part or use their documented rendering property."},{framework:"webComponents",part:"Empty",kind:"slot",name:"no-results",type:"Content projection",description:"The message shown while a query filtered the side to nothing. This framework spells the content customization as a native named slot/snippet on this part; consumers of other wrappers compose the corresponding part or use their documented rendering property."},{framework:"webComponents",part:"Item",name:"pos-in-set",type:"number | undefined",description:"This option's 1-based position in the whole option set, published as `aria-posinset`; set together with `setSize` for a virtualized or paged listbox. This is documented configuration/state on the native element or its composed part beyond the common component core. It is explicitly recorded as an element adapter rather than falsely requiring the same property binding on every wrapper.",kind:"prop"},{framework:"webComponents",part:"Item",name:"set-size",type:"number | undefined",description:"Size of the full option set this option belongs to. See posInSet. This is documented configuration/state on the native element or its composed part beyond the common component core. It is explicitly recorded as an element adapter rather than falsely requiring the same property binding on every wrapper.",kind:"prop"},{framework:"webComponents",part:"List",kind:"method",name:"selectItem",type:"(value: string) => void",description:"Route listbox commit to root's marked set. Web Components exposes this documented state/action on its element for native JavaScript and composed elements. Other wrappers coordinate their parts through context, injected state or callback props; this instance helper is separate from the common interaction contract."},{framework:"webComponents",part:"List",kind:"method",name:"setActiveValue",type:"(value: string | null) => void",description:"Sync the roving focus position with the root transfer element. Web Components exposes this documented state/action on its element for native JavaScript and composed elements. Other wrappers coordinate their parts through context, injected state or callback props; this instance helper is separate from the common interaction contract."},{framework:"webComponents",part:"List",name:"allow-select-all",type:"boolean",description:"Enables Control+A to select all, or clear all if already selected, while `multiple`; inert when single-select. This is documented configuration/state on the native element or its composed part beyond the common component core. It is explicitly recorded as an element adapter rather than falsely requiring the same property binding on every wrapper.",kind:"prop"},{framework:"webComponents",part:"List",name:"disallow-empty-selection",type:"boolean",description:"Prevents the last selected option from being deselected while `multiple`, including Control+A's clear. This is documented configuration/state on the native element or its composed part beyond the common component core. It is explicitly recorded as an element adapter rather than falsely requiring the same property binding on every wrapper.",kind:"prop"},{framework:"webComponents",part:"List",name:"focus-on-hover",type:"boolean",description:"Moves roving focus to options on hover; off by default. This is documented configuration/state on the native element or its composed part beyond the common component core. It is explicitly recorded as an element adapter rather than falsely requiring the same property binding on every wrapper.",kind:"prop"},{framework:"webComponents",part:"List",name:"multiple",type:"boolean",description:"Allows more than one option to be selected: `value` becomes a `string[]`\nand choosing an option toggles it instead of replacing the selection. This is documented configuration/state on the native element or its composed part beyond the common component core. It is explicitly recorded as an element adapter rather than falsely requiring the same property binding on every wrapper.",kind:"prop"},{framework:"webComponents",part:"List",name:"options",type:"SkdxOptionsInput | undefined",description:"Data-driven option collection: when present and no light-DOM\n`<skdx-listbox-*>` children are authored, the listbox derives its own\nItems from it \u2014 a flat list, or `{ label, options }` groups. This is documented configuration/state on the native element or its composed part beyond the common component core. It is explicitly recorded as an element adapter rather than falsely requiring the same property binding on every wrapper.",kind:"prop"},{framework:"webComponents",part:"List",name:"orientation",type:"'horizontal' | 'vertical'",description:"Arrow-key navigation axis \u2014 `vertical` uses Up/Down, `horizontal` uses Left/Right \u2014 reflected as `aria-orientation`/`data-orientation`. This is documented configuration/state on the native element or its composed part beyond the common component core. It is explicitly recorded as an element adapter rather than falsely requiring the same property binding on every wrapper.",kind:"prop"},{framework:"webComponents",part:"List",kind:"slot",name:"option",type:"Content projection",description:"Per-option render content, addressed by `data-value`. This framework spells the content customization as a native named slot/snippet on this part; consumers of other wrappers compose the corresponding part or use their documented rendering property."},{framework:"webComponents",name:"anchor",type:"Record<TransferSide, string | null>",description:"The last option marked *without* Shift \u2014 the fixed end of a Shift range. This documented getter exposes the native element\u2019s derived live state. Other wrappers expose their state through context or framework refs; the shared component contract does not require identical getter spellings.",kind:"prop"},{framework:"angular",part:"Empty",name:"contentTemplate",type:"TemplateRef<SkdxTransferEmptyContext>",description:'Template wording the message, receiving an SkdxTransferEmptyContext so "empty" and "no match" can read differently. This wrapper receives projected content as a render prop, Snippet or TemplateRef. Other wrappers supply the matching named slot or default content of the composed part; the property is framework rendering syntax.'},{framework:"webComponents",part:"Empty",kind:"slot",name:"empty",type:"Content projection",description:"The message shown while the side genuinely holds nothing. This framework spells the content customization as a native named slot/snippet on this part; consumers of other wrappers compose the corresponding part or use their documented rendering property."},{framework:"webComponents",part:"Empty",kind:"slot",name:"no-results",type:"Content projection",description:"The message shown while a query filtered the side to nothing. This framework spells the content customization as a native named slot/snippet on this part; consumers of other wrappers compose the corresponding part or use their documented rendering property."},{framework:"webComponents",part:"Item",name:"pos-in-set",type:"number | undefined",description:"This option's 1-based position in the whole option set, published as `aria-posinset`; set together with `setSize` for a virtualized or paged listbox. This is documented configuration/state on the native element or its composed part beyond the common component core. It is explicitly recorded as an element adapter rather than falsely requiring the same property binding on every wrapper."},{framework:"webComponents",part:"Item",name:"set-size",type:"number | undefined",description:"Size of the full option set this option belongs to. See posInSet. This is documented configuration/state on the native element or its composed part beyond the common component core. It is explicitly recorded as an element adapter rather than falsely requiring the same property binding on every wrapper."},{framework:"webComponents",part:"List",kind:"method",name:"selectItem",type:"(value: string) => void",description:"Route listbox commit to root's marked set. Web Components exposes this documented state/action on its element for native JavaScript and composed elements. Other wrappers coordinate their parts through context, injected state or callback props; this instance helper is separate from the common interaction contract."},{framework:"webComponents",part:"List",kind:"method",name:"setActiveValue",type:"(value: string | null) => void",description:"Sync the roving focus position with the root transfer element. Web Components exposes this documented state/action on its element for native JavaScript and composed elements. Other wrappers coordinate their parts through context, injected state or callback props; this instance helper is separate from the common interaction contract."},{framework:"webComponents",part:"List",name:"allow-select-all",type:"boolean",description:"Enables Control+A to select all, or clear all if already selected, while `multiple`; inert when single-select. This is documented configuration/state on the native element or its composed part beyond the common component core. It is explicitly recorded as an element adapter rather than falsely requiring the same property binding on every wrapper."},{framework:"webComponents",part:"List",name:"disallow-empty-selection",type:"boolean",description:"Prevents the last selected option from being deselected while `multiple`, including Control+A's clear. This is documented configuration/state on the native element or its composed part beyond the common component core. It is explicitly recorded as an element adapter rather than falsely requiring the same property binding on every wrapper."},{framework:"webComponents",part:"List",name:"focus-on-hover",type:"boolean",description:"Moves roving focus to options on hover; off by default. This is documented configuration/state on the native element or its composed part beyond the common component core. It is explicitly recorded as an element adapter rather than falsely requiring the same property binding on every wrapper."},{framework:"webComponents",part:"List",name:"multiple",type:"boolean",description:"Allows more than one option to be selected: `value` becomes a `string[]`\nand choosing an option toggles it instead of replacing the selection. This is documented configuration/state on the native element or its composed part beyond the common component core. It is explicitly recorded as an element adapter rather than falsely requiring the same property binding on every wrapper."},{framework:"webComponents",part:"List",name:"options",type:"SkdxOptionsInput | undefined",description:"Data-driven option collection: when present and no light-DOM\n`<skdx-listbox-*>` children are authored, the listbox derives its own\nItems from it \u2014 a flat list, or `{ label, options }` groups. This is documented configuration/state on the native element or its composed part beyond the common component core. It is explicitly recorded as an element adapter rather than falsely requiring the same property binding on every wrapper."},{framework:"webComponents",part:"List",name:"orientation",type:"'horizontal' | 'vertical'",description:"Arrow-key navigation axis \u2014 `vertical` uses Up/Down, `horizontal` uses Left/Right \u2014 reflected as `aria-orientation`/`data-orientation`. This is documented configuration/state on the native element or its composed part beyond the common component core. It is explicitly recorded as an element adapter rather than falsely requiring the same property binding on every wrapper."},{framework:"webComponents",part:"List",kind:"slot",name:"option",type:"Content projection",description:"Per-option render content, addressed by `data-value`. This framework spells the content customization as a native named slot/snippet on this part; consumers of other wrappers compose the corresponding part or use their documented rendering property."},{framework:"webComponents",name:"anchor",type:"Record<TransferSide, string | null>",description:"The last option marked *without* Shift \u2014 the fixed end of a Shift range. This documented getter exposes the native element\u2019s derived live state. Other wrappers expose their state through context or framework refs; the shared component contract does not require identical getter spellings."}]};export{r as transferContract};
@@ -1 +1 @@
1
- import{changeDetail as n}from"./change-reason.js";import{webComponentsValidationMethods as a}from"./form-validity.js";import{dismissalEvents as o,forceMountProp as e,openChangeCompleteEvent as r,popperContentProps as i,popperMeasurementProperties as s,portalPart as t,portalPropsSansForceMount as l}from"./overlay-parts.js";import{VIRTUAL_FOCUS_RULE as d}from"./rules.js";import{PART_DATA_ATTRIBUTES as h}from"./styling-hooks.js";import{withoutScope as c}from"./types.js";var w={name:"TreeSelect",purpose:'A picker that chooses one or many nodes from hierarchical data with tri-state cascading checks and optional filter. The trigger is role="combobox"; the popup holds an optional filter input and role="tree" with virtual focus and announced match counts, laid out either as one indented tree or, under layout="columns", as one tree per open level side by side. Without hand-composed children, the root derives the anatomy from collection.',props:[{name:"renderNode",type:"(node: TreeSelectNode) => React.ReactNode",required:!1,frameworks:["react","vue","svelte","angular"],exception:"render-prop",frameworksReason:'Web Components uses a named light-DOM slot (`slot="node"`) rather than a function property, since HTML attributes cannot carry functions. Angular ships a `TemplateRef` input instead of a function. Content falls back to plain label when no slotted node matches.',description:"Renders a node's content in the derived anatomy, forwarded to the Tree part's own renderLabel so the two never drift."},{name:"placeholder",type:"string",required:!1,description:"Stand-in content the derived trigger's Value shows while nothing is checked."},{name:"aria-label",type:"string",required:!1,frameworks:["react","vue","svelte","angular"],exception:"attribute-passthrough",frameworksReason:"All five packages settle one resolution rule: the derived name is `aria-label ?? placeholder`. React, Vue, and Svelte declare it as a prop; Angular uses `ariaLabel`. Web Components claims the native `aria-label` attribute off the host and relocates it onto the derived control.",description:"Accessible name shared by the derived Trigger, filter Input, and Tree, each taking no name from content or from each other."},{name:"emptyMessage",type:"string",required:!1,defaultValue:"'No matches'",description:"Content for the derived Empty part, shown when the filter matches nothing."},{name:"collection",type:"ReadonlyArray<{ value: string; label?: string; disabled?: boolean; hasChildren?: boolean; children?: TreeSelectNode[] }>",required:!0,description:`The hierarchical data the popup's tree is built from; node values must be unique across the whole collection. A childless node is a leaf unless it sets hasChildren, which declares it a branch while its children are still unloaded, so it reports aria-expanded="false" and can be expanded; cascading treats such a branch as a leaf.`},{name:"value",type:"string[]",required:!1,description:"Controlled checked node values, always an array in the shape valueStrategy describes, paired with the value-change event. Activating the node that is already the single checked one clears the value, since single-select has no other way back to empty; otherwise a consumer's own button calling the value-change handler with [] is the clear affordance, as no part renders one."},{name:"defaultValue",type:"string[]",defaultValue:"[]",required:!1,description:"Initially checked node values for uncontrolled usage, in the same shape as value."},{name:"multiple",type:"boolean",defaultValue:"false",required:!1,description:"Allows more than one node to be checked, adding aria-multiselectable and toggle-based activation."},{name:"checkStrategy",type:"'cascade' | 'descendants' | 'ancestors' | 'independent'",defaultValue:"'cascade'",required:!1,description:"Whether, and in which direction, checking a node propagates through the hierarchy around it. 'cascade' propagates both ways, making it a tri-state tree; 'descendants' is the downward half alone, what a permissions tree wants; 'ancestors' is the upward half alone, what a category filter wants; 'independent' propagates nothing. Ignored unless multiple."},{name:"valueStrategy",type:"'all' | 'leaf' | 'shallowest'",defaultValue:"'all'",required:!1,description:"Which checked nodes appear in the emitted value: all, leaves only, or the shallowest node that implies a fully-checked subtree. Ignored (treated as 'all') wherever checkStrategy lacks the direction that would rebuild what the projection drops \u2014 'leaf' needs the upward one, 'shallowest' the downward one, and 'independent' has neither."},{name:"expanded",type:"string[]",required:!1,description:'Controlled expanded node ids, paired with the expanded-change event. While filtering, the ancestors of every match are expanded on top of this value without being written back to it. Under layout="columns" this same set is also which columns are showing \u2014 one column per expanded branch along a single open path.'},{name:"defaultExpanded",type:"string[]",defaultValue:"[]",required:!1,description:"Initially expanded node ids for uncontrolled usage; every branch starts collapsed."},{name:"layout",type:"'tree' | 'columns'",defaultValue:"'tree'",required:!1,description:`How the popup presents the hierarchy, changing nothing about the data. 'tree' is one indented role="tree" whose ArrowRight expands a branch in place. 'columns' is the cascader: one role="tree" per open level, side by side, whose ArrowRight opens the branch's column and moves focus into it. Both layouts share the same collection, checking, value projection, lazy loading and expanded state. While the filter text is non-empty, 'columns' renders the tree layout instead, because a match drags its whole ancestor chain into view and that has no column structure.`},{name:"open",type:"boolean",required:!1,description:"Controlled open state of the popup, paired with the open-change event."},{name:"defaultOpen",type:"boolean",defaultValue:"false",required:!1,description:"Initial open state, for uncontrolled usage."},{name:"inputValue",type:"string",required:!1,description:"Controlled filter text for the Input part, paired with the input-value-change event."},{name:"defaultInputValue",type:"string",defaultValue:"''",required:!1,description:"Initial filter text, for uncontrolled usage \u2014 unfiltered."},{name:"filter",type:"(node: TreeSelectNode, inputValue: string) => boolean",required:!1,description:"Decides whether a node matches the filter text; a match keeps its whole ancestor chain and subtree visible, which is why a filtered columns layout renders as a tree."},{name:"getValueLabel",type:"(value: string) => string",required:!1,description:"Supplies the label for a value whose node is not yet in collection, such as a lazily-loaded saved value."},{name:"expandOnClick",type:"boolean",defaultValue:"false",required:!1,description:'Whether clicking a branch row also toggles its expansion, instead of leaving that to the chevron alone. Ignored (treated as true) under layout="columns", where the row is the only thing that opens a column and a false value would leave the mouse no way to reach the next one.'},{name:"loadChildren",type:"(nodeValue: string) => void | Promise<void>",required:!1,description:`Loads a node's children the first time it is expanded, for a collection not fully materialised up front. Mark the branch with hasChildren so it is expandable before anything is under it; the node carries aria-busy="true" and data-loading until the returned promise settles, and a rejection leaves it eligible to retry. Loading does not run while filtering, which cannot match nodes that have not arrived.`},{name:"disabled",type:"boolean",defaultValue:"false",required:!1,description:"Blocks the whole control: the trigger is a disabled button, the popup cannot open, and no key changes anything."},{name:"name",type:"string",required:!1,description:"Form field name; with one set, every selected node value submits as its own entry under formData.getAll(name)."},{name:"form",type:"string",required:!1,description:"Id of the form the submitted entries belong to, for a TreeSelect that is not a DOM descendant of it."},{name:"readOnly",type:"boolean",defaultValue:"false",required:!1,description:"Lets the user open, navigate and read the tree but never change the value \u2014 unlike disabled, which also removes the control from the tab order."},{name:"closeOnSelect",type:"boolean",defaultValue:"!multiple",required:!1,description:"Closes the popup as soon as a node is checked, returning focus to the trigger."},{name:"getMatchCountLabel",type:"(count: number) => string",required:!1,description:"Formats the message announced each time the number of filter matches changes while the popup is open."},{name:"size",type:"'sm' | 'lg' | (string & {})",required:!1,description:"Size rung forwarded to the popup Content panel's data-size; the attribute may be written on Content directly instead."}],events:[{name:"value-change",description:"Fired with the whole new value whenever the checked set changes, projected through valueStrategy rather than the toggled node.",detail:n("string[]","selectedValue: string | null")},{name:"expanded-change",description:"Fired with the new expanded set whenever a branch is expanded or collapsed, controlled or not. Never fires for the transient expansion filtering adds.",detail:"string[]"},{name:"open-change",detail:"boolean",description:"Fired with the new open state whenever the popup opens or closes, controlled or not."},{name:"input-value-change",description:"Fired with the new filter text as the user types, controlled or not.",detail:"string"},r("popup"),...o("popup")],parts:[{name:"Trigger",lightDom:!0,description:'The role="combobox" button that opens the popup, carrying aria-expanded, aria-controls, aria-haspopup="tree", and \u2014 while no Input is mounted \u2014 aria-activedescendant naming the virtually-focused node.'},{name:"Value",description:"The trigger's read-out of the current selection, rendering one data-chip span per value while multiple.",props:[{name:"placeholder",type:"string",required:!1,description:"Stand-in content rendered while nothing at all is checked."}]},{...t,description:`${t.description} Not shipped by Angular, which positions with strategy: 'fixed' instead; Web Components hides parts via the hidden attribute rather than unmounting.`,frameworks:["react","vue","svelte","webComponents"],exception:"portal",frameworksReason:"Angular Content positions with strategy: 'fixed' to escape clipping ancestors, so no Portal is needed. Web Components hides parts via the hidden attribute rather than unmounting.",props:[...l,{...e,frameworks:["react","vue","svelte"],exception:"force-mount",frameworksReason:"Web Components parts never unmount; they hide via `hidden`, so force-mount has nothing to do."}]},{name:"Content",description:"The popup panel, positioned against the trigger and dismissed by Escape or an outside interaction; carries no ARIA role of its own.",props:[...i({align:"'start'",sideOffset:"4"}),{...e,frameworks:["react","vue","svelte"],exception:"force-mount",frameworksReason:"Angular and Web Components keep this panel mounted while closed, hidden via `[hidden]`/the hidden attribute, so force-mount has nothing to do."},{name:"size",type:"'sm' | 'lg' | (string & {})",required:!1,description:"Visual size rung, written to data-size on the popup panel; the data-size attribute may be set directly instead."}]},{name:"Input",lightDom:!0,description:"The optional filter text field. When mounted, DOM focus and aria-activedescendant move from the trigger to the input. Printable keys become filter text; Space types a character rather than selecting."},{name:"Columns",lightDom:!0,description:'The horizontal track holding one Tree per open level while layout is "columns". It carries role="presentation", because each column Tree keeps the semantics; it is not rendered in the tree layout, nor while a filter is narrowing the popup.',props:[{name:"labelTemplate",type:"TemplateRef<TreeSelectLabelContext>",required:!1,frameworks:["angular"],exception:"render-prop",frameworksReason:"Angular alone needs a member here: the other four pass their renderLabel function to each column Tree as content, which a TemplateRef cannot be.",description:"Template rendering each node's label and adjacent content, forwarded to every column Tree."}]},{name:"Tree",lightDom:!0,description:'The role="tree" inside the popup, rendering one Item per visible node in navigation order with aria-level, aria-posinset, and aria-setsize. Under layout="columns" the root renders one Tree per open level inside Columns, each listing a single sibling set, named by the branch it belongs to and marked with data-skdx-tree-parent-id \u2014 absent on the root column.',props:[{name:"renderLabel",type:"(node: TreeSelectNode) => unknown",required:!1,frameworks:["react","vue","svelte","webComponents"],exception:"render-prop",frameworksReason:"Angular ships this member as `labelTemplate`, a TemplateRef taking the same context \u2014 a render function has no Angular equivalent. Member-name matching alone cannot see this per-prop rename, so without this note an implemented member could appear undocumented.",description:"Renders a node's content \u2014 label, chevron, or checkmark. Defaults to the node's label, falling back to its value. Angular uses `labelTemplate` instead of a function."},{name:"rows",type:"ReadonlyArray<{ value: string; depth: number }>",required:!1,description:"Renders this row set instead of the whole collection. Rows render flat, one Item each with depth standing in for aria-level. aria-posinset/aria-setsize are still measured against the unwindowed collection. One column of the columns layout is exactly such a row set: a single sibling set at one depth."}],methods:[{name:"invalidate",signature:"(nodeId?: string): void",description:"Clears cached lazy-load state for one node, or for the whole tree when no id is given, so its children load again on next expand."}]},{name:"Item",lightDom:!0,description:'One role="treeitem" row, normally rendered by Tree. Reports aria-checked (true, false, or mixed under cascade) as its only selection attribute, aria-expanded on branches only, data-highlighted while virtually-focused, and aria-busy with data-loading while loadChildren is pending.',props:[{name:"node",type:"TreeSelectNode",required:!0,description:"The collection node this row stands for."}]},{name:"ItemIndicator",description:"The checkmark shown on a checked \u2014 or, under cascade, partially checked \u2014 node. Reads its state from the surrounding Item, so one indicator markup covers all three states.",props:[c(e)]},{name:"Empty",description:"Shown in place of the tree when the filter matches nothing."}],slots:[{name:"trigger",description:"Trigger's content \u2014 a Value and any decoration, such as a chevron."},{name:"default",description:"Content's body \u2014 an optional Input, the Tree (or a Columns track of them), and an Empty state."}],appearance:[{attribute:"data-size",values:["sm","lg"],stylesheet:"tree-select.css"}],states:["closed","open","checked","unchecked","mixed","highlighted","placeholder","disabled","readonly","loading"],controlledUncontrolled:'Four independent axes \u2014 value, expanded, open, and inputValue. Filter-driven expansion is layered on top of expanded and never written back to it. layout is a fifth, uncontrolled axis with no event: it is how the popup is drawn, not state the component changes. Under layout="columns" expanded keeps its type and its event but describes a single open path, so expanding a node collapses any other branch at the same depth; an expanded set naming two branches at one depth renders the first in collection order and ignores the rest rather than failing.',formBehavior:"Submits repeated same-name entries (one per selected value) so formData.getAll(name) reads them back. Empty selection submits nothing; there is no required flag. React, Vue, Svelte and Angular render one visually-hidden native control per value. Web Components uses ElementInternals.setFormValue instead.",validationBehavior:'None of its own: there is no required flag, so nothing native to fail. Rules like "choose at least one" belong to the surrounding Field/Form, which reads the value array directly.',accessibility:{role:"combobox (Trigger), tree (Tree), treeitem (Item)",keyboard:[{key:"ArrowDown",behavior:`Opens the popup and moves to the first node when closed; moves to the next visible node when open. Under layout="columns" it walks the active column alone and stays on that column's last enabled node rather than wrapping or crossing into another column.`},{key:"ArrowUp",behavior:`Opens the popup and moves to the last node when closed; moves to the previous visible node when open. Under layout="columns" it walks the active column alone and stays on that column's first enabled node.`},{key:"ArrowRight",behavior:`Expands a collapsed branch, moves to the first child of an expanded one, or moves to the next node when there is nothing to expand. Mirrored under dir="rtl", where ArrowLeft expands. Under layout="columns" one press does the whole move: it opens the active branch's column and makes that column's first enabled node active. On a leaf it does nothing at all \u2014 there is no column to the right. On a branch whose children are still loading it opens the empty column and leaves the branch active, moving in once the children arrive if the branch is still the active node; a column that loads with no enabled node leaves it active too.`},{key:"ArrowLeft",behavior:'Collapses an expanded branch, or moves to its parent when already collapsed. Mirrored under dir="rtl". Under layout="columns" it is the exact inverse of ArrowRight: the parent branch in the previous column becomes active and the column being left, with every column right of it, closes. In the first column it does nothing.'},{key:"Home / End",behavior:'Move to the first/last visible node while the popup is open \u2014 the first/last enabled node of the active column under layout="columns", since the column is the navigable unit there.'},{key:"*",behavior:`Expands every collapsed sibling of the active node \u2014 the nodes at the same level under the same parent. Visual focus does not move. Does nothing under layout="columns", where only one branch per level can be open at a time. The rest of the tree pattern's collection keys are deliberately absent: Shift+Arrow range extension is what a plain Tree needs and this does not, because cascade already checks a whole branch in one keystroke, and Ctrl+A over a cascading tree is "check everything", which is what checking the root already means.`},{key:"Enter",behavior:"Opens the popup when closed; checks or unchecks the active node when open, branch included and in either layout \u2014 opening a branch is ArrowRight's job, never Enter's."},{key:"Space",behavior:"Checks or unchecks the active node from the trigger. With an Input mounted it types a space instead \u2014 it is a character the user is entering, not a selection."},{key:"Typing a letter (on the Trigger)",behavior:'Jumps to the next matching visible node (typeahead). Only while open, and only with no Input mounted, where a printable key is filter text instead. Under layout="columns" it searches the active column alone, so a match never teleports the user into a column they cannot see.'},{key:"Escape",behavior:"Closes the popup and returns focus to the trigger, in both layouts \u2014 it never closes one column first."},{key:"Tab",behavior:"Closes the popup and lets focus move out of the control \u2014 deliberately not prevented."}],ariaAttributes:['role="combobox" with aria-expanded and aria-haspopup="tree" on Trigger, plus aria-controls naming the Tree. Only while the popup is mounted, since a dangling IDREF tells a screen reader there is no popup.',"aria-activedescendant on whichever element holds DOM focus \u2014 the Trigger, or the Input when one is mounted \u2014 naming the virtually-focused treeitem",'role="tree" with aria-multiselectable while multiple, and aria-readonly while readOnly, on the single tree and on every column tree alike',"aria-level, aria-posinset, and aria-setsize on every treeitem, describing its place in the hierarchy",`Under layout="columns" each column is its own role="tree" listing one sibling set, rather than one tree spanning the panels. A single tree cannot contain columns it does not enclose without aria-owns reparenting every level, which is the least reliably supported relation in ARIA; N trees keep the hierarchy in the attributes instead. aria-level therefore still carries the node's true depth and aria-posinset/aria-setsize are measured within the column, which is the node's whole sibling set \u2014 so depth survives the loss of the indentation that mirrored it, and each column is announced by name as focus enters it.`,'aria-controls on the anchor names the column tree that holds the active node and follows it across columns, because aria-activedescendant may only name a descendant of the element the combobox controls. aria-haspopup stays "tree" in both layouts, since every panel is still a tree.','aria-checked on each treeitem: true, false, or mixed for a partially-checked branch under cascade. Never emitted with aria-selected; a multiple TreeSelect sets selectionMode="none" and keeps aria-multiselectable on the tree.',`aria-expanded on branch nodes only, including nodes declaring hasChildren with unloaded children; under layout="columns" it is true exactly while that branch's column is showing`,'aria-busy="true" on a node whose loadChildren call is still pending',"aria-disabled and data-disabled on a disabled node and its whole subtree, which is skipped by navigation and excluded from cascade propagation",'Match counts are announced politely as the filter narrows and once on open. The Empty part is role="presentation".'],focusManagement:`${d} Here the anchor is the Trigger, or the Input when one is mounted. Opening seeds the active node from the first visible node in value, falling back to the first enabled row. Keyboard moves bring the active node into view via scrollIntoView({ block: "nearest" }). Escape and closeOnSelect return focus to the Trigger; Tab closes without preventing focus from leaving. The columns layout changes none of this: the same anchor keeps DOM focus, and exactly one Item in the whole popup carries data-highlighted, so the active column is the one a skin matches with :has([data-highlighted]) and needs no attribute of its own. Crossing a column rewrites aria-activedescendant and aria-controls in the same update, and scrolling adds inline: "nearest" so the new column comes into view. Opening still expands nothing \u2014 the seed is the first value already sitting in a showing column, falling back to the root column's first enabled node.`,namingRequirement:`The Trigger must be named by a label or aria-label. The Input needs its own name. The Tree needs a name too \u2014 APG requires one for any role="tree", and it cannot be inherited from the Trigger or the nodes inside it. Under layout="columns" that obligation multiplies: every column tree needs its own name, taken from the branch whose children it lists, while the root column takes the control's name.`},styling:{cssParts:["trigger","value","chip","content","input","columns","tree","item","item-indicator","empty"],dataAttributes:[...h,"data-state",{name:"data-layout",description:'On Content, and on the Columns track when one renders: the layout actually drawn, so it reads "tree" while a filter narrows a columns popup rather than the layout prop that was asked for.'},"data-checked","data-highlighted","data-loading","data-placeholder","data-multiple","data-disabled","data-readonly","data-value","data-side","data-align","data-chip","data-size","data-skdx-tree-node-id","data-skdx-tree-parent-id",{name:"data-skdx-tree-select-generated",frameworks:["webComponents"],exception:"wc-marker",frameworksReason:'Marks a node the element generated for itself rather than one the consumer authored. React, Vue, Svelte and Angular re-render their derived anatomy declaratively, so "mine or theirs" is answered by the framework and no marker is needed.'}],cssCustomProperties:[...s("tree-select"),"--skdx-tree-node-depth"]},publicMethods:[...a]};export{w as treeSelectContract};
1
+ import{changeDetail as n}from"./change-reason.js";import{webComponentsValidationMethods as a}from"./form-validity.js";import{dismissalEvents as o,forceMountProp as e,openChangeCompleteEvent as r,popperContentProps as i,popperMeasurementProperties as s,portalPart as t,portalPropsSansForceMount as l}from"./overlay-parts.js";import{VIRTUAL_FOCUS_RULE as d}from"./rules.js";import{PART_DATA_ATTRIBUTES as h}from"./styling-hooks.js";import{withoutScope as c}from"./types.js";var b={name:"TreeSelect",purpose:'A picker that chooses one or many nodes from hierarchical data with tri-state cascading checks and optional filter. The trigger is role="combobox"; the popup holds an optional filter input and role="tree" with virtual focus and announced match counts, laid out either as one indented tree or, under layout="columns", as one tree per open level side by side. Without hand-composed children, the root derives the anatomy from collection.',props:[{name:"renderNode",type:"(node: TreeSelectNode) => React.ReactNode",required:!1,frameworks:["react","vue","svelte","angular"],exception:"render-prop",frameworksReason:'Web Components uses a named light-DOM slot (`slot="node"`) rather than a function property, since HTML attributes cannot carry functions. Angular ships a `TemplateRef` input instead of a function. Content falls back to plain label when no slotted node matches.',description:"Renders a node's content in the derived anatomy, forwarded to the Tree part's own renderLabel so the two never drift."},{name:"placeholder",type:"string",required:!1,description:"Stand-in content the derived trigger's Value shows while nothing is checked."},{name:"aria-label",type:"string",required:!1,frameworks:["react","vue","svelte","angular"],exception:"attribute-passthrough",frameworksReason:"All five packages settle one resolution rule: the derived name is `aria-label ?? placeholder`. React, Vue, and Svelte declare it as a prop; Angular uses `ariaLabel`. Web Components claims the native `aria-label` attribute off the host and relocates it onto the derived control.",description:"Accessible name shared by the derived Trigger, filter Input, and Tree, each taking no name from content or from each other."},{name:"emptyMessage",type:"string",required:!1,defaultValue:"'No matches'",description:"Content for the derived Empty part, shown when the filter matches nothing."},{name:"collection",type:"ReadonlyArray<{ value: string; label?: string; disabled?: boolean; hasChildren?: boolean; children?: TreeSelectNode[] }>",required:!0,description:`The hierarchical data the popup's tree is built from; node values must be unique across the whole collection. A childless node is a leaf unless it sets hasChildren, which declares it a branch while its children are still unloaded, so it reports aria-expanded="false" and can be expanded; cascading treats such a branch as a leaf.`},{name:"value",type:"string[]",required:!1,description:"Controlled checked node values, always an array in the shape valueStrategy describes, paired with the value-change event. Activating the node that is already the single checked one clears the value, since single-select has no other way back to empty; otherwise a consumer's own button calling the value-change handler with [] is the clear affordance, as no part renders one."},{name:"defaultValue",type:"string[]",defaultValue:"[]",required:!1,description:"Initially checked node values for uncontrolled usage, in the same shape as value."},{name:"multiple",type:"boolean",defaultValue:"false",required:!1,description:"Allows more than one node to be checked, adding aria-multiselectable and toggle-based activation."},{name:"checkStrategy",type:"'cascade' | 'descendants' | 'ancestors' | 'independent'",defaultValue:"'cascade'",required:!1,description:"Whether, and in which direction, checking a node propagates through the hierarchy around it. 'cascade' propagates both ways, making it a tri-state tree; 'descendants' is the downward half alone, what a permissions tree wants; 'ancestors' is the upward half alone, what a category filter wants; 'independent' propagates nothing. Ignored unless multiple."},{name:"valueStrategy",type:"'all' | 'leaf' | 'shallowest'",defaultValue:"'all'",required:!1,description:"Which checked nodes appear in the emitted value: all, leaves only, or the shallowest node that implies a fully-checked subtree. Ignored (treated as 'all') wherever checkStrategy lacks the direction that would rebuild what the projection drops \u2014 'leaf' needs the upward one, 'shallowest' the downward one, and 'independent' has neither."},{name:"expanded",type:"string[]",required:!1,description:'Controlled expanded node ids, paired with the expanded-change event. While filtering, the ancestors of every match are expanded on top of this value without being written back to it. Under layout="columns" this same set is also which columns are showing \u2014 one column per expanded branch along a single open path.'},{name:"defaultExpanded",type:"string[]",defaultValue:"[]",required:!1,description:"Initially expanded node ids for uncontrolled usage; every branch starts collapsed."},{name:"layout",type:"'tree' | 'columns'",defaultValue:"'tree'",required:!1,description:`How the popup presents the hierarchy, changing nothing about the data. 'tree' is one indented role="tree" whose ArrowRight expands a branch in place. 'columns' is the cascader: one role="tree" per open level, side by side, whose ArrowRight opens the branch's column and moves focus into it. Both layouts share the same collection, checking, value projection, lazy loading and expanded state. While the filter text is non-empty, 'columns' renders the tree layout instead, because a match drags its whole ancestor chain into view and that has no column structure.`},{name:"open",type:"boolean",required:!1,description:"Controlled open state of the popup, paired with the open-change event."},{name:"defaultOpen",type:"boolean",defaultValue:"false",required:!1,description:"Initial open state, for uncontrolled usage."},{name:"inputValue",type:"string",required:!1,description:"Controlled filter text for the Input part, paired with the input-value-change event."},{name:"defaultInputValue",type:"string",defaultValue:"''",required:!1,description:"Initial filter text, for uncontrolled usage \u2014 unfiltered."},{name:"filter",type:"(node: TreeSelectNode, inputValue: string) => boolean",required:!1,description:"Decides whether a node matches the filter text; a match keeps its whole ancestor chain and subtree visible, which is why a filtered columns layout renders as a tree."},{name:"getValueLabel",type:"(value: string) => string",required:!1,description:"Supplies the label for a value whose node is not yet in collection, such as a lazily-loaded saved value."},{name:"expandOnClick",type:"boolean",defaultValue:"false",required:!1,description:'Whether clicking a branch row also toggles its expansion, instead of leaving that to the chevron alone. Ignored (treated as true) under layout="columns", where the row is the only thing that opens a column and a false value would leave the mouse no way to reach the next one.'},{name:"loadChildren",type:"(nodeValue: string) => void | Promise<void>",required:!1,description:`Loads a node's children the first time it is expanded, for a collection not fully materialised up front. Mark the branch with hasChildren so it is expandable before anything is under it; the node carries aria-busy="true" and data-loading until the returned promise settles, and a rejection leaves it eligible to retry. Loading does not run while filtering, which cannot match nodes that have not arrived.`},{name:"disabled",type:"boolean",defaultValue:"false",required:!1,description:"Blocks the whole control: the trigger is a disabled button, the popup cannot open, and no key changes anything."},{name:"name",type:"string",required:!1,description:"Form field name; with one set, every selected node value submits as its own entry under formData.getAll(name)."},{name:"form",type:"string",required:!1,description:"Id of the form the submitted entries belong to, for a TreeSelect that is not a DOM descendant of it."},{name:"readOnly",type:"boolean",defaultValue:"false",required:!1,description:"Lets the user open, navigate and read the tree but never change the value \u2014 unlike disabled, which also removes the control from the tab order."},{name:"closeOnSelect",type:"boolean",defaultValue:"!multiple",required:!1,description:"Closes the popup as soon as a node is checked, returning focus to the trigger."},{name:"getMatchCountLabel",type:"(count: number) => string",required:!1,description:"Formats the message announced each time the number of filter matches changes while the popup is open."},{name:"size",type:"'sm' | 'lg' | (string & {})",required:!1,description:"Size rung forwarded to the popup Content panel's data-size; the attribute may be written on Content directly instead."}],events:[{name:"value-change",description:"Fired with the whole new value whenever the checked set changes, projected through valueStrategy rather than the toggled node.",detail:n("string[]","selectedValue: string | null")},{name:"expanded-change",description:"Fired with the new expanded set whenever a branch is expanded or collapsed, controlled or not. Never fires for the transient expansion filtering adds.",detail:"string[]"},{name:"open-change",detail:"boolean",description:"Fired with the new open state whenever the popup opens or closes, controlled or not."},{name:"input-value-change",description:"Fired with the new filter text as the user types, controlled or not.",detail:"string"},r("popup"),...o("popup")],parts:[{name:"Trigger",lightDom:!0,description:'The role="combobox" button that opens the popup, carrying aria-expanded, aria-controls, aria-haspopup="tree", and \u2014 while no Input is mounted \u2014 aria-activedescendant naming the virtually-focused node.',slots:[{name:"default",description:"Trigger's content \u2014 a Value and any decoration, such as a chevron."}]},{name:"Value",description:"The trigger's read-out of the current selection, rendering one data-chip span per value while multiple.",props:[{name:"placeholder",type:"string",required:!1,description:"Stand-in content rendered while nothing at all is checked."}]},{...t,description:`${t.description} Not shipped by Angular, which positions with strategy: 'fixed' instead; Web Components hides parts via the hidden attribute rather than unmounting.`,frameworks:["react","vue","svelte","webComponents"],exception:"portal",frameworksReason:"Angular Content positions with strategy: 'fixed' to escape clipping ancestors, so no Portal is needed. Web Components hides parts via the hidden attribute rather than unmounting.",props:[...l,{...e,frameworks:["react","vue","svelte"],exception:"force-mount",frameworksReason:"Web Components parts never unmount; they hide via `hidden`, so force-mount has nothing to do."}]},{name:"Content",description:"The popup panel, positioned against the trigger and dismissed by Escape or an outside interaction; carries no ARIA role of its own.",props:[...i({align:"'start'",sideOffset:"4"}),{...e,frameworks:["react","vue","svelte"],exception:"force-mount",frameworksReason:"Angular and Web Components keep this panel mounted while closed, hidden via `[hidden]`/the hidden attribute, so force-mount has nothing to do."},{name:"size",type:"'sm' | 'lg' | (string & {})",required:!1,description:"Visual size rung, written to data-size on the popup panel; the data-size attribute may be set directly instead."}]},{name:"Input",lightDom:!0,description:"The optional filter text field. When mounted, DOM focus and aria-activedescendant move from the trigger to the input. Printable keys become filter text; Space types a character rather than selecting."},{name:"Columns",lightDom:!0,description:'The horizontal track holding one Tree per open level while layout is "columns". It carries role="presentation", because each column Tree keeps the semantics; it is not rendered in the tree layout, nor while a filter is narrowing the popup.',props:[{name:"labelTemplate",type:"TemplateRef<TreeSelectLabelContext>",required:!1,frameworks:["angular"],exception:"render-prop",frameworksReason:"Angular alone needs a member here: the other four pass their renderLabel function to each column Tree as content, which a TemplateRef cannot be.",description:"Template rendering each node's label and adjacent content, forwarded to every column Tree."}]},{name:"Tree",lightDom:!0,description:'The role="tree" inside the popup, rendering one Item per visible node in navigation order with aria-level, aria-posinset, and aria-setsize. Under layout="columns" the root renders one Tree per open level inside Columns, each listing a single sibling set, named by the branch it belongs to and marked with data-skdx-tree-parent-id \u2014 absent on the root column.',props:[{name:"renderLabel",type:"(node: TreeSelectNode) => unknown",required:!1,frameworks:["react","vue","svelte","webComponents"],exception:"render-prop",frameworksReason:"Angular ships this member as `labelTemplate`, a TemplateRef taking the same context \u2014 a render function has no Angular equivalent. Member-name matching alone cannot see this per-prop rename, so without this note an implemented member could appear undocumented.",description:"Renders a node's content \u2014 label, chevron, or checkmark. Defaults to the node's label, falling back to its value. Angular uses `labelTemplate` instead of a function."},{name:"rows",type:"ReadonlyArray<{ value: string; depth: number }>",required:!1,description:"Renders this row set instead of the whole collection. Rows render flat, one Item each with depth standing in for aria-level. aria-posinset/aria-setsize are still measured against the unwindowed collection. One column of the columns layout is exactly such a row set: a single sibling set at one depth."}],methods:[{name:"invalidate",signature:"(nodeId?: string): void",description:"Clears cached lazy-load state for one node, or for the whole tree when no id is given, so its children load again on next expand."}]},{name:"Item",lightDom:!0,description:'One role="treeitem" row, normally rendered by Tree. Reports aria-checked (true, false, or mixed under cascade) as its only selection attribute, aria-expanded on branches only, data-highlighted while virtually-focused, and aria-busy with data-loading while loadChildren is pending.',props:[{name:"node",type:"TreeSelectNode",required:!0,description:"The collection node this row stands for."}]},{name:"ItemIndicator",description:"The checkmark shown on a checked \u2014 or, under cascade, partially checked \u2014 node. Reads its state from the surrounding Item, so one indicator markup covers all three states.",props:[c(e)]},{name:"Empty",description:"Shown in place of the tree when the filter matches nothing."}],slots:[{name:"default",description:"Content's body \u2014 an optional Input, the Tree (or a Columns track of them), and an Empty state."},{name:"node",frameworks:["webComponents"],exception:"render-prop",frameworksReason:"webComponents expose node as native content projection for the renderNode rendering capability. Other frameworks pass the documented renderNode render prop or template instead of a named slot with this spelling.",description:"Renders a node's content in the derived anatomy, forwarded to the Tree part's own renderLabel so the two never drift."}],appearance:[{attribute:"data-size",values:["sm","lg"],stylesheet:"tree-select.css"}],states:["closed","open","checked","unchecked","mixed","highlighted","placeholder","disabled","readonly","loading"],controlledUncontrolled:'Four independent axes \u2014 value, expanded, open, and inputValue. Filter-driven expansion is layered on top of expanded and never written back to it. layout is a fifth, uncontrolled axis with no event: it is how the popup is drawn, not state the component changes. Under layout="columns" expanded keeps its type and its event but describes a single open path, so expanding a node collapses any other branch at the same depth; an expanded set naming two branches at one depth renders the first in collection order and ignores the rest rather than failing.',formBehavior:"Submits repeated same-name entries (one per selected value) so formData.getAll(name) reads them back. Empty selection submits nothing; there is no required flag. React, Vue, Svelte and Angular render one visually-hidden native control per value. Web Components uses ElementInternals.setFormValue instead.",validationBehavior:'None of its own: there is no required flag, so nothing native to fail. Rules like "choose at least one" belong to the surrounding Field/Form, which reads the value array directly.',accessibility:{role:"combobox (Trigger), tree (Tree), treeitem (Item)",keyboard:[{key:"ArrowDown",behavior:`Opens the popup and moves to the first node when closed; moves to the next visible node when open. Under layout="columns" it walks the active column alone and stays on that column's last enabled node rather than wrapping or crossing into another column.`},{key:"ArrowUp",behavior:`Opens the popup and moves to the last node when closed; moves to the previous visible node when open. Under layout="columns" it walks the active column alone and stays on that column's first enabled node.`},{key:"ArrowRight",behavior:`Expands a collapsed branch, moves to the first child of an expanded one, or moves to the next node when there is nothing to expand. Mirrored under dir="rtl", where ArrowLeft expands. Under layout="columns" one press does the whole move: it opens the active branch's column and makes that column's first enabled node active. On a leaf it does nothing at all \u2014 there is no column to the right. On a branch whose children are still loading it opens the empty column and leaves the branch active, moving in once the children arrive if the branch is still the active node; a column that loads with no enabled node leaves it active too.`},{key:"ArrowLeft",behavior:'Collapses an expanded branch, or moves to its parent when already collapsed. Mirrored under dir="rtl". Under layout="columns" it is the exact inverse of ArrowRight: the parent branch in the previous column becomes active and the column being left, with every column right of it, closes. In the first column it does nothing.'},{key:"Home / End",behavior:'Move to the first/last visible node while the popup is open \u2014 the first/last enabled node of the active column under layout="columns", since the column is the navigable unit there.'},{key:"*",behavior:`Expands every collapsed sibling of the active node \u2014 the nodes at the same level under the same parent. Visual focus does not move. Does nothing under layout="columns", where only one branch per level can be open at a time. The rest of the tree pattern's collection keys are deliberately absent: Shift+Arrow range extension is what a plain Tree needs and this does not, because cascade already checks a whole branch in one keystroke, and Ctrl+A over a cascading tree is "check everything", which is what checking the root already means.`},{key:"Enter",behavior:"Opens the popup when closed; checks or unchecks the active node when open, branch included and in either layout \u2014 opening a branch is ArrowRight's job, never Enter's."},{key:"Space",behavior:"Checks or unchecks the active node from the trigger. With an Input mounted it types a space instead \u2014 it is a character the user is entering, not a selection."},{key:"Typing a letter (on the Trigger)",behavior:'Jumps to the next matching visible node (typeahead). Only while open, and only with no Input mounted, where a printable key is filter text instead. Under layout="columns" it searches the active column alone, so a match never teleports the user into a column they cannot see.'},{key:"Escape",behavior:"Closes the popup and returns focus to the trigger, in both layouts \u2014 it never closes one column first."},{key:"Tab",behavior:"Closes the popup and lets focus move out of the control \u2014 deliberately not prevented."}],ariaAttributes:['role="combobox" with aria-expanded and aria-haspopup="tree" on Trigger, plus aria-controls naming the Tree. Only while the popup is mounted, since a dangling IDREF tells a screen reader there is no popup.',"aria-activedescendant on whichever element holds DOM focus \u2014 the Trigger, or the Input when one is mounted \u2014 naming the virtually-focused treeitem",'role="tree" with aria-multiselectable while multiple, and aria-readonly while readOnly, on the single tree and on every column tree alike',"aria-level, aria-posinset, and aria-setsize on every treeitem, describing its place in the hierarchy",`Under layout="columns" each column is its own role="tree" listing one sibling set, rather than one tree spanning the panels. A single tree cannot contain columns it does not enclose without aria-owns reparenting every level, which is the least reliably supported relation in ARIA; N trees keep the hierarchy in the attributes instead. aria-level therefore still carries the node's true depth and aria-posinset/aria-setsize are measured within the column, which is the node's whole sibling set \u2014 so depth survives the loss of the indentation that mirrored it, and each column is announced by name as focus enters it.`,'aria-controls on the anchor names the column tree that holds the active node and follows it across columns, because aria-activedescendant may only name a descendant of the element the combobox controls. aria-haspopup stays "tree" in both layouts, since every panel is still a tree.','aria-checked on each treeitem: true, false, or mixed for a partially-checked branch under cascade. Never emitted with aria-selected; a multiple TreeSelect sets selectionMode="none" and keeps aria-multiselectable on the tree.',`aria-expanded on branch nodes only, including nodes declaring hasChildren with unloaded children; under layout="columns" it is true exactly while that branch's column is showing`,'aria-busy="true" on a node whose loadChildren call is still pending',"aria-disabled and data-disabled on a disabled node and its whole subtree, which is skipped by navigation and excluded from cascade propagation",'Match counts are announced politely as the filter narrows and once on open. The Empty part is role="presentation".'],focusManagement:`${d} Here the anchor is the Trigger, or the Input when one is mounted. Opening seeds the active node from the first visible node in value, falling back to the first enabled row. Keyboard moves bring the active node into view via scrollIntoView({ block: "nearest" }). Escape and closeOnSelect return focus to the Trigger; Tab closes without preventing focus from leaving. The columns layout changes none of this: the same anchor keeps DOM focus, and exactly one Item in the whole popup carries data-highlighted, so the active column is the one a skin matches with :has([data-highlighted]) and needs no attribute of its own. Crossing a column rewrites aria-activedescendant and aria-controls in the same update, and scrolling adds inline: "nearest" so the new column comes into view. Opening still expands nothing \u2014 the seed is the first value already sitting in a showing column, falling back to the root column's first enabled node.`,namingRequirement:`The Trigger must be named by a label or aria-label. The Input needs its own name. The Tree needs a name too \u2014 APG requires one for any role="tree", and it cannot be inherited from the Trigger or the nodes inside it. Under layout="columns" that obligation multiplies: every column tree needs its own name, taken from the branch whose children it lists, while the root column takes the control's name.`},styling:{cssParts:["trigger","value","chip","content","input","columns","tree","item","item-indicator","empty"],dataAttributes:[...h,"data-state",{name:"data-layout",description:'On Content, and on the Columns track when one renders: the layout actually drawn, so it reads "tree" while a filter narrows a columns popup rather than the layout prop that was asked for.'},"data-checked","data-highlighted","data-loading","data-placeholder","data-multiple","data-disabled","data-readonly","data-value","data-side","data-align","data-chip","data-size","data-skdx-tree-node-id","data-skdx-tree-parent-id",{name:"data-skdx-tree-select-generated",frameworks:["webComponents"],exception:"wc-marker",frameworksReason:'Marks a node the element generated for itself rather than one the consumer authored. React, Vue, Svelte and Angular re-render their derived anatomy declaratively, so "mine or theirs" is answered by the framework and no marker is needed.'}],cssCustomProperties:[...s("tree-select"),"--skdx-tree-node-depth"]},publicMethods:[...a]};export{b as treeSelectContract};