@skdx/docs-kit 0.38.0 → 0.39.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.
@@ -1 +1 @@
1
- import{changeDetail as r}from"./change-reason.js";import{DATE_CHANGE_VALUE_TYPE as o,DATE_INPUT_RULE as e,DATE_MALFORMED_RULE as a,DATE_OUTPUT_RULE as n,DATE_PARSE_RULE as i}from"./date-value.js";import{ROOT_DATA_ATTRIBUTES as s}from"./styling-hooks.js";var t="Activating a cell in the month or year grid navigates and selects nothing: value is untouched and value-change never fires from a non-day grid, because value is a Date \u2014 a day \u2014 and a month or a year is not one. What it does instead is project focusedDate onto the chosen unit and step the view down one level (year -> month -> day), stopping at minView. The projection preserves the day-of-month under exactly the clamp paging already uses: the focused day-of-month is carried onto the chosen month and clamped to that month's last day, so picking February while the 31st is focused lands on the 28th (or the 29th) rather than spilling into March, and picking a non-leap year while February 29th is focused lands on February 28th. The displayed month follows the projection whenever it leaves every displayed month, firing month-change, so the day grid always opens on the month that was just picked; the moved cursor is reported through focus-change, and the step down through view-change.",c={name:"Calendar",purpose:"Month-grid date picker following WAI-ARIA grid, built on native Date math and Intl. No date-library dependency or bundled name tables. Selects single date, multiple dates, or start/end range over consecutive months. Derives complete anatomy (header, day grids, month/year views) or accepts child parts.",props:[{name:"selectionMode",type:"'single' | 'multiple' | 'range'",defaultValue:"'single'",required:!1,description:"Selection count and value shape: 'single' \u2192 Date | null, 'multiple' \u2192 Date[], 'range' \u2192 { start: Date; end: Date | null } | null. Uses union type because frameworks cannot discriminate on sibling props."},{name:"value",type:"Date | string | Array<Date | string> | { start: Date | string; end: Date | string | null } | null",required:!1,description:`Current selection, for controlled usage. Its shape follows selectionMode. ${e} The wire string is self-describing across all three modes: "2026-08-10" is a single date, "2026-08-10,2026-08-14" a set, "2026-08-10/2026-08-14" a range, and a trailing slash a range still anchored on its start. ${i} ${a}`},{name:"defaultValue",type:"Date | string | Array<Date | string> | { start: Date | string; end: Date | string | null } | null",defaultValue:"null for `single`/`range`, `[]` for `multiple`.",required:!1,description:`Initial selection, for uncontrolled usage. Its shape follows selectionMode, and it takes the same Date-or-ISO-string forms value does. ${e}`},{name:"month",type:"Date | string",required:!1,description:`Displayed month, for controlled usage. ${e} A bare "2026-08" is accepted alongside "2026-08-10"; either way only the year and month are read.`},{name:"defaultMonth",type:"Date | string",required:!1,description:`Initially displayed month, for uncontrolled usage. Defaults to value's month, or the current month. Takes the same Date-or-ISO-string forms month does, including the bare "2026-08".`},{name:"minDate",type:"Date | string",required:!1,description:`Earliest selectable date; earlier dates render disabled. ${e} ${a}`},{name:"maxDate",type:"Date | string",required:!1,description:`Latest selectable date; later dates render disabled. ${e} ${a}`},{name:"weekStartsOn",type:"0 | 1 | 2 | 3 | 4 | 5 | 6",defaultValue:"derived from locale via Intl.Locale week info, falling back to 0 (Sunday)",required:!1,description:"Day of week the grid's first column represents. Derived from locale via Intl.Locale week info when omitted."},{name:"pageBehavior",type:"'visible' | 'single'",defaultValue:"'visible'",required:!1,description:"'visible' pages by numberOfMonths months; 'single' moves one month. Only observable with numberOfMonths above 1."},{name:"today",type:"Date | string",defaultValue:"new Date(), read once when the calendar mounts",required:!1,description:'The date the calendar treats as today: the cell carrying data-today and aria-current="date". Read once at mount, not per render, to prevent cells straddling midnight. Necessary in server-rendered apps to prevent hydration mismatch.'},{name:"locale",type:"string",defaultValue:"the runtime's own locale",required:!1,description:"BCP 47 tag to derive month and weekday names via Intl.DateTimeFormat and default weekStartsOn. No implementation ships hardcoded month or day name. Grid is Gregorian because value is a native Date; non-Gregorian calendars render localized Gregorian names over Gregorian month grid."},{name:"numberOfMonths",type:"number",defaultValue:"1",required:!1,description:"Consecutive months to display. Render one Grid and Heading per month, each with matching monthOffset."},{name:"readOnly",type:"boolean",defaultValue:"false",required:!1,description:'Non-editable: every cell stays focusable with tab stop, grid carries aria-readonly="true", and no interaction changes selection. Distinct from isDateDisabled (per-date) and disabled (removes tab order).'},{name:"disabled",type:"boolean",defaultValue:"false",required:!1,description:'Disables the whole calendar: cells are not focusable, grid leaves tab order, carries aria-disabled="true". Use isDateDisabled for per-date disabling.'},{name:"isDateDisabled",type:"(date: Date) => boolean",required:!1,description:"Mark dates as disabled beyond minDate/maxDate. Disabled cell is not focusable, not clickable. Takes a Date, same as isDateUnavailable."},{name:"isDateUnavailable",type:"(date: Date) => boolean",required:!1,description:'Mark dates as unavailable. Unlike isDateDisabled, an unavailable cell stays keyboard-reachable and is announced via aria-disabled="true", allowing screen-reader users to land on it.'},{name:"focusedDate",type:"Date | string",required:!1,description:`The date holding the grid's roving tab stop, for controlled usage \u2014 the one cell with tabIndex 0. Distinct from value: focus moves with every arrow key and selects nothing, which is exactly why a calendar wanting to restore a cursor, or to mirror one between two calendars, cannot do it through value. ${e} ${a}`},{name:"defaultFocusedDate",type:"Date | string",defaultValue:"value's anchor date, else the displayed month's first day",required:!1,description:`Initially focused date for uncontrolled usage. ${e}`},{name:"outsideDaySelectable",type:"boolean",defaultValue:"false",required:!1,description:"Whether a day from an adjacent month can be picked. False (default) disables those cells; true enables them, keeps data-outside-month hook, and pages calendar to their month on selection."},{name:"weekNumbers",type:"boolean",defaultValue:"false",required:!1,description:'Whether each week row is labelled with its ISO-8601 week number. Independent of weekStartsOn: week numbering and grid start column answer different questions. Number renders as role="rowheader" cell with role="columnheader" above it, so body rows sit under matching header.'},{name:"view",type:"'day' | 'month' | 'year'",required:!1,description:`Which grid is showing, for controlled usage: the day grid, the twelve-month grid, or the twenty-year grid. ${t}`},{name:"defaultView",type:"'day' | 'month' | 'year'",defaultValue:"'day'",required:!1,description:"Which grid shows first, for uncontrolled usage. Clamped to minView/maxView."},{name:"minView",type:"'day' | 'month' | 'year'",defaultValue:"'day'",required:!1,description:"Most granular view the calendar steps down to. Raising it restricts navigation: with 'month' the day grid is unreachable."},{name:"maxView",type:"'day' | 'month' | 'year'",defaultValue:"'year'",required:!1,description:"Least granular view the calendar steps up to and how far the Heading trigger goes. Never below minView."},{name:"getSelectionAnnouncement",type:"(value: CalendarValue, locale: string | undefined) => string",required:!1,description:'Announce selection changes in multiple and range mode. Return empty string to announce nothing. Defaults to English wording e.g. "Range selected, 1 May to 8 May."'},{name:"getMonthAnnouncement",type:"(month: Date, locale: string | undefined) => string",defaultValue:"formatMonthLabel",required:!1,description:'Announce navigation button paging. Return empty string to announce nothing. Defaults to locale-formatted month label, e.g. "f\xE9vrier 2026"'}],events:[{name:"value-change",detail:r(o),description:`Fires with new selection when a cell is chosen. Never fires while readOnly or disabled. ${n}`},{name:"month-change",detail:"Date",description:"Fires with newly displayed month as a Date when navigation changes it."},{name:"focus-change",detail:"Date",description:`Fires with newly focused date when grid's roving tab stop moves (arrow key, Home/End, PageUp/PageDown, click, paging). Independent of value-change. ${n}`},{name:"view-change",detail:"'day' | 'month' | 'year'",description:"Fires with view now showing ('day', 'month', 'year') when Heading steps up or non-day cell steps down. Independent of month-change and focus-change."}],parts:[{name:"Grid",description:`role="grid" container for one month of cells, owning arrow-key navigation. One per displayed month. Renders only while view is 'day'.`,props:[{name:"monthOffset",type:"number",defaultValue:"0",required:!1,description:"Which of the numberOfMonths displayed months this grid renders, 0 being the first. Decides which dates its cells treat as outside-month."},{name:"label",type:"string",required:!1,frameworks:["angular","webComponents"],exception:"attribute-passthrough",frameworksReason:"React, Vue and Svelte take `aria-label` on the element through attribute passthrough.",description:"Overrides the grid's accessible name, the locale-formatted month by default."}]},{name:"GridHead",description:'role="rowgroup" wrapper for the weekday header. Renders weekday row with short name and full name as aria-label.'},{name:"GridBody",description:'role="rowgroup" wrapper around the week rows.'},{name:"WeekNumber",description:'role="rowheader" cell with ISO-8601 week number, rendered at week row start when weekNumbers is set. rowheader rather than gridcell because the number labels its row.',props:[{name:"date",type:"Date | string",required:!0,description:`Any day of the week to number \u2014 the row's first cell is what GridBody passes. ${e}`}]},{name:"WeekNumberHeader",description:'role="columnheader" above the WeekNumber column, rendered when weekNumbers is set. Visually blank, named via aria-label.'},{name:"MonthGrid",lightDom:!0,description:`role="grid" container: twelve gridcells (three rows of four), one per month of focused year. Shows while view is 'month'. Full grid with accessible name, roving tabindex, arrow/Home/End/PageUp/PageDown at month unit. Unavailable months are aria-disabled="true", keeping them in roving order. ${t}`,props:[{name:"label",type:"string",required:!1,frameworks:["angular","webComponents"],exception:"attribute-passthrough",frameworksReason:"React, Vue and Svelte take `aria-label` on the element through attribute passthrough.",description:"Overrides the grid's accessible name, the locale-formatted month page by default."}]},{name:"YearGrid",lightDom:!0,description:`role="grid" container: twenty gridcells (five rows of four), one twenty-year page. Shows while view is 'year'. Same full grid model and aria-disabled rule as MonthGrid, one unit coarser. ${t}`,props:[{name:"label",type:"string",required:!1,frameworks:["angular","webComponents"],exception:"attribute-passthrough",frameworksReason:"React, Vue and Svelte take `aria-label` on the element through attribute passthrough.",description:"Overrides the grid's accessible name, the locale-formatted year page by default."}]},{name:"PreviousButton",description:'Button paging the calendar back one unit of the current view, named "Previous month", "Previous year" or "Previous 20 years" by default.',props:[{name:"label",type:"string",required:!1,frameworks:["angular","webComponents"],exception:"attribute-passthrough",frameworksReason:"React, Vue and Svelte take `aria-label` on the element through attribute passthrough.",description:"Accessible name for the button, replacing the view-dependent default."}]},{name:"NextButton",description:'Button paging the calendar forward one unit of the current view, named "Next month", "Next year" or "Next 20 years" by default.',props:[{name:"label",type:"string",required:!1,frameworks:["angular","webComponents"],exception:"attribute-passthrough",frameworksReason:"React, Vue and Svelte take `aria-label` on the element through attribute passthrough.",description:"Accessible name for the button, replacing the view-dependent default."}]},{name:"Heading",description:"Label for what is on screen and view-up trigger. Renders as button stepping view up when below maxView, otherwise plain label.",props:[{name:"monthOffset",type:"number",defaultValue:"0",required:!1,description:"Which of the displayed months to label, 0 being the first."}]},{name:"Cell",description:'role="gridcell" date button carrying state hooks (selected, today, outside-month, disabled, unavailable, data-range-*).',props:[{name:"date",type:"Date | string",required:!0,description:`The day this cell renders. Required and undefaulted: a cell has no meaning without one, and there is no sensible fallback \u2014 the grid supplies it while iterating a week, so it is the one prop every consumer writes. ${e}`}]}],slots:[{name:"default",description:"Replaces the derived anatomy entirely. Left empty, the calendar draws all of it. Takes the Calendar parts."},{name:"heading",description:"Displayed month's label; empty for locale-derived default."},{name:"cell",description:"A single selectable date cell."}],states:["default","selected","today","outside-month","disabled","unavailable","range-start","range-middle","range-end"],controlledUncontrolled:"value, month, focusedDate and view each work controlled or uncontrolled, independently of one another.",formBehavior:"Not applicable \u2014 Calendar does not itself submit; pair with DatePicker for a form-field trigger.",validationBehavior:"Not applicable \u2014 Calendar performs no validation beyond min/max/disabled/unavailable cell rendering. Range and multiple selections are announced through the shared LiveAnnouncer (the anchored start, then the completed range; the running count in multiple mode), because that state lives across several cells and no single aria-selected flip conveys it.",accessibility:{role:'grid (the date grid), rowgroup (GridHead and GridBody), row (each week and the weekday-name row), columnheader (each weekday name, and the week-number column when weekNumbers is on), rowheader (each week number), gridcell (each date). GridHead is a rowgroup, NOT a row, and GridBody is a rowgroup, not a bare wrapper \u2014 a role="row" nested in a role="row", or a plain div between the grid and its rows, is what axe caught as aria-required-children / aria-required-parent in all five frameworks; the implementations were fixed to match.',keyboard:[{key:"ArrowLeft/ArrowRight",behavior:"Move focus by one day."},{key:"ArrowUp/ArrowDown",behavior:"Move focus by one week."},{key:"Home/End",behavior:"Jump to the start/end of the focused week."},{key:"PageUp/PageDown",behavior:"Move focus by one month. Unlike the navigation buttons, which page by pageBehavior, the keyboard always moves a single month: it moves the focused *date*, and paging it by numberOfMonths would jump the cursor rather than the view."},{key:"Shift+PageUp/PageDown",behavior:"Move focus by one year."},{key:"ArrowLeft/ArrowRight (month/year grid)",behavior:"Move focus by one month, or by one year."},{key:"ArrowUp/ArrowDown (month/year grid)",behavior:"Move focus by one row \u2014 four months, or four years, since both grids are four columns wide."},{key:"Home/End (month/year grid)",behavior:"Jump to the first/last cell of the page: January/December, or the twenty-year page's first/last year."},{key:"PageUp/PageDown (month/year grid)",behavior:"Move focus by one whole page \u2014 a year in the month grid, twenty years in the year grid \u2014 and announce the new page, since paging replaces every cell and moves no focus assistive technology would report."},{key:"Shift+PageUp/PageDown (month/year grid)",behavior:"Move focus by ten pages \u2014 ten years, or two hundred."},{key:"Enter/Space (month/year grid)",behavior:"Navigate to the focused month or year and step the view down one level. Selects nothing: only the day grid selects."},{key:"Enter/Space",behavior:"Select the focused date (native button activation). In range mode the first press anchors the range start and the second closes it, ordering start/end so a backwards selection still yields start <= end; a third press starts over."}],ariaAttributes:["aria-selected on every gridcell in the selection \u2014 in range mode that is every date from start to end inclusive, not just the two endpoints",'aria-disabled="true" on an unavailable (but still focusable) gridcell',`aria-current="date" on the gridcell representing today, and on no other cell \u2014 the sighted-only data-today hook is not a substitute, because inside a grid of bare day numbers today is the one part of a cell's meaning that cannot be read off its own text`,"aria-multiselectable on the grid, false only in single-selection mode",'an accessible name on the grid naming its month, from the same Intl formatter the Heading uses \u2014 role="grid" is name-from-author only, so an unnamed one announces as a bare "grid", and with numberOfMonths above 1 there are several identical-sounding grids on screen. Emitted as aria-label and suppressed the moment the consumer supplies an aria-label or aria-labelledby of their own',`aria-label on every gridcell carrying the full localized date ("Monday, January 5, 2026"), from the same Intl formatter the selection announcement uses. A cell's own text is a bare day number, so an unlabelled cell announces as "14" with no month \u2014 which makes arrowing into an adjacent month, and a PageDown that changes it, completely silent. Localized by locale rather than by a prop, because it is derived from the value rather than invented by the widget`,'aria-readonly="true" on the grid while readOnly, and absent otherwise \u2014 a read-only calendar keeps every cell focusable and every tab stop, so this attribute is the only thing that distinguishes it from an editable one','aria-disabled="true" on the grid while the whole calendar is disabled, and absent otherwise \u2014 every cell is natively disabled and therefore silent, so the grid is the only element left to carry the state','an accessible name on MonthGrid and YearGrid too, for the same name-from-author reason the day grid needs one: the focused year ("2026") and the year page ("2020 - 2039"), from the same values the Heading renders, so the two never disagree about what is on screen',`aria-label on every month and year gridcell carrying the full localized unit name ("January 2026", "2026") \u2014 a month cell's own text is an abbreviated month name and a year cell's is four digits, neither of which says which year or which page it belongs to`,'aria-disabled="true" on a month or year gridcell no date can satisfy, which keeps it focusable rather than removing it from the grid \u2014 the unavailable model, not the disabled one, because a twelve-cell grid with silent gaps reads as a broken grid'],focusManagement:"A controlled value that moves to a date outside every displayed month takes the focused date and the displayed month with it, firing month-change \u2014 otherwise the grid keeps its only tab stop on a cell that is no longer rendered, and Tab reaches the calendar to land on nothing. Roving tabindex: only the currently focused date is a tab stop (tabIndex 0), all others -1, and that date is readable and settable as focusedDate with every move reported through focus-change. Unavailable dates keep their place in the roving order; disabled ones do not, and an outside-month day joins it only while outsideDaySelectable is on. A disabled calendar has no tab stop at all, because every cell carries the native disabled attribute. The month and year grids run the same roving model one unit coarser: the tab stop sits on the cell focusedDate falls in \u2014 its month, or its year \u2014 so the cursor survives every step up and down the view stack rather than resetting, and a cell no date can satisfy keeps its place in that order because it is aria-disabled rather than natively disabled.",namingRequirement:'Weekday columnheaders render the locale short name with the full locale name as aria-label, so screen readers say "Monday" rather than "Mon".'},styling:{cssParts:["calendar","header","heading","previous-button","next-button","grid","row","week-day","cell","grid-head","grid-body","month-grid","year-grid","view-cell","week-number","week-number-header"],dataAttributes:[...s,"data-state","data-today","data-outside-month","data-disabled","data-unavailable","data-range-start","data-range-middle","data-range-end","data-skdx-calendar-cell","data-skdx-calendar-view-cell",{name:"data-skdx-calendar-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:"setValue",signature:"(date: Date) => void",frameworks:["webComponents"],exception:"imperative",frameworksReason:"Only the custom element publishes it. The other four re-render with a new value 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:"Moves the selection to date and emits value-change, exactly as picking that cell would."}]};export{t as CALENDAR_PROJECTION_RULE,c as calendarContract};
1
+ import{changeDetail as r}from"./change-reason.js";import{DATE_CHANGE_VALUE_TYPE as o,DATE_INPUT_RULE as e,DATE_MALFORMED_RULE as a,DATE_OUTPUT_RULE as n,DATE_PARSE_RULE as i}from"./date-value.js";import{ROOT_DATA_ATTRIBUTES as s}from"./styling-hooks.js";var t="Activating a cell in the month or year grid navigates and selects nothing: value is untouched and value-change never fires from a non-day grid, because value is a Date \u2014 a day \u2014 and a month or a year is not one. What it does instead is project focusedDate onto the chosen unit and step the view down one level (year -> month -> day), stopping at minView. The projection preserves the day-of-month under exactly the clamp paging already uses: the focused day-of-month is carried onto the chosen month and clamped to that month's last day, so picking February while the 31st is focused lands on the 28th (or the 29th) rather than spilling into March, and picking a non-leap year while February 29th is focused lands on February 28th. The displayed month follows the projection whenever it leaves every displayed month, firing month-change, so the day grid always opens on the month that was just picked; the moved cursor is reported through focus-change, and the step down through view-change.",c={name:"Calendar",purpose:"Month-grid date picker following WAI-ARIA grid, built on native Date math and Intl. No date-library dependency or bundled name tables. Selects single date, multiple dates, or start/end range over consecutive months. Derives complete anatomy (header, day grids, month/year views) or accepts child parts.",props:[{name:"selectionMode",type:"'single' | 'multiple' | 'range'",defaultValue:"'single'",required:!1,description:"Selection count and value shape: 'single' \u2192 Date | null, 'multiple' \u2192 Date[], 'range' \u2192 { start: Date; end: Date | null } | null. Uses union type because frameworks cannot discriminate on sibling props."},{name:"value",type:"Date | string | Array<Date | string> | { start: Date | string; end: Date | string | null } | null",required:!1,description:`Current selection, for controlled usage. Its shape follows selectionMode. ${e} The wire string is self-describing across all three modes: "2026-08-10" is a single date, "2026-08-10,2026-08-14" a set, "2026-08-10/2026-08-14" a range, and a trailing slash a range still anchored on its start. ${i} ${a}`},{name:"defaultValue",type:"Date | string | Array<Date | string> | { start: Date | string; end: Date | string | null } | null",defaultValue:"null for `single`/`range`, `[]` for `multiple`.",required:!1,description:`Initial selection, for uncontrolled usage. Its shape follows selectionMode, and it takes the same Date-or-ISO-string forms value does. ${e}`},{name:"month",type:"Date | string",required:!1,description:`Displayed month, for controlled usage. ${e} A bare "2026-08" is accepted alongside "2026-08-10"; either way only the year and month are read.`},{name:"defaultMonth",type:"Date | string",required:!1,description:`Initially displayed month, for uncontrolled usage. Defaults to value's month, or the current month. Takes the same Date-or-ISO-string forms month does, including the bare "2026-08".`},{name:"minDate",type:"Date | string",required:!1,description:`Earliest selectable date; earlier dates render disabled. ${e} ${a}`},{name:"maxDate",type:"Date | string",required:!1,description:`Latest selectable date; later dates render disabled. ${e} ${a}`},{name:"weekStartsOn",type:"0 | 1 | 2 | 3 | 4 | 5 | 6",defaultValue:"derived from locale via Intl.Locale week info, falling back to 0 (Sunday)",required:!1,description:"Day of week the grid's first column represents. Derived from locale via Intl.Locale week info when omitted."},{name:"pageBehavior",type:"'visible' | 'single'",defaultValue:"'visible'",required:!1,description:"'visible' pages by numberOfMonths months; 'single' moves one month. Only observable with numberOfMonths above 1."},{name:"today",type:"Date | string",defaultValue:"new Date(), read once when the calendar mounts",required:!1,description:'The date the calendar treats as today: the cell carrying data-today and aria-current="date". Read once at mount, not per render, to prevent cells straddling midnight. Necessary in server-rendered apps to prevent hydration mismatch.'},{name:"locale",type:"string",defaultValue:"the runtime's own locale",required:!1,description:"BCP 47 tag to derive month and weekday names via Intl.DateTimeFormat and default weekStartsOn. No implementation ships hardcoded month or day name. Grid is Gregorian because value is a native Date; non-Gregorian calendars render localized Gregorian names over Gregorian month grid."},{name:"numberOfMonths",type:"number",defaultValue:"1",required:!1,description:"Consecutive months to display. Render one Grid and Heading per month, each with matching monthOffset."},{name:"readOnly",type:"boolean",defaultValue:"false",required:!1,description:'Non-editable: every cell stays focusable with tab stop, grid carries aria-readonly="true", and no interaction changes selection. Distinct from isDateDisabled (per-date) and disabled (removes tab order).'},{name:"disabled",type:"boolean",defaultValue:"false",required:!1,description:'Disables the whole calendar: cells are not focusable, grid leaves tab order, carries aria-disabled="true". Use isDateDisabled for per-date disabling.'},{name:"isDateDisabled",type:"(date: Date) => boolean",required:!1,description:"Mark dates as disabled beyond minDate/maxDate. Disabled cell is not focusable, not clickable. Takes a Date, same as isDateUnavailable."},{name:"isDateUnavailable",type:"(date: Date) => boolean",required:!1,description:'Mark dates as unavailable. Unlike isDateDisabled, an unavailable cell stays keyboard-reachable and is announced via aria-disabled="true", allowing screen-reader users to land on it.'},{name:"focusedDate",type:"Date | string",required:!1,description:`The date holding the grid's roving tab stop, for controlled usage \u2014 the one cell with tabIndex 0. Distinct from value: focus moves with every arrow key and selects nothing, which is exactly why a calendar wanting to restore a cursor, or to mirror one between two calendars, cannot do it through value. ${e} ${a}`},{name:"defaultFocusedDate",type:"Date | string",defaultValue:"value's anchor date, else today when it is displayed, else the month's first day",required:!1,description:`Initially focused date for uncontrolled usage. ${e}`},{name:"outsideDaySelectable",type:"boolean",defaultValue:"false",required:!1,description:"Whether a day from an adjacent month can be picked. False (default) disables those cells; true enables them, keeps data-outside-month hook, and pages calendar to their month on selection."},{name:"weekNumbers",type:"boolean",defaultValue:"false",required:!1,description:'Whether each week row is labelled with its ISO-8601 week number. Independent of weekStartsOn: week numbering and grid start column answer different questions. Number renders as role="rowheader" cell with role="columnheader" above it, so body rows sit under matching header.'},{name:"view",type:"'day' | 'month' | 'year'",required:!1,description:`Which grid is showing, for controlled usage: the day grid, the twelve-month grid, or the twenty-year grid. ${t}`},{name:"defaultView",type:"'day' | 'month' | 'year'",defaultValue:"'day'",required:!1,description:"Which grid shows first, for uncontrolled usage. Clamped to minView/maxView."},{name:"minView",type:"'day' | 'month' | 'year'",defaultValue:"'day'",required:!1,description:"Most granular view the calendar steps down to. Raising it restricts navigation: with 'month' the day grid is unreachable."},{name:"maxView",type:"'day' | 'month' | 'year'",defaultValue:"'year'",required:!1,description:"Least granular view the calendar steps up to and how far the Heading trigger goes. Never below minView."},{name:"getSelectionAnnouncement",type:"(value: CalendarValue, locale: string | undefined) => string",required:!1,description:'Announce selection changes in multiple and range mode. Return empty string to announce nothing. Defaults to English wording e.g. "Range selected, 1 May to 8 May."'},{name:"getMonthAnnouncement",type:"(month: Date, locale: string | undefined) => string",defaultValue:"formatMonthLabel",required:!1,description:'Announce navigation button paging. Return empty string to announce nothing. Defaults to locale-formatted month label, e.g. "f\xE9vrier 2026"'}],events:[{name:"value-change",detail:r(o),description:`Fires with new selection when a cell is chosen. Never fires while readOnly or disabled. ${n}`},{name:"month-change",detail:"Date",description:"Fires with newly displayed month as a Date when navigation changes it."},{name:"focus-change",detail:"Date",description:`Fires with newly focused date when grid's roving tab stop moves (arrow key, Home/End, PageUp/PageDown, click, paging). Independent of value-change. ${n}`},{name:"view-change",detail:"'day' | 'month' | 'year'",description:"Fires with view now showing ('day', 'month', 'year') when Heading steps up or non-day cell steps down. Independent of month-change and focus-change."}],parts:[{name:"Grid",description:`role="grid" container for one month of cells, owning arrow-key navigation. One per displayed month. Renders only while view is 'day'.`,props:[{name:"monthOffset",type:"number",defaultValue:"0",required:!1,description:"Which of the numberOfMonths displayed months this grid renders, 0 being the first. Decides which dates its cells treat as outside-month."},{name:"label",type:"string",required:!1,frameworks:["angular","webComponents"],exception:"attribute-passthrough",frameworksReason:"React, Vue and Svelte take `aria-label` on the element through attribute passthrough.",description:"Overrides the grid's accessible name, the locale-formatted month by default."}]},{name:"GridHead",description:'role="rowgroup" wrapper for the weekday header. Renders weekday row with short name and full name as aria-label.'},{name:"GridBody",description:'role="rowgroup" wrapper around the week rows.'},{name:"WeekNumber",description:'role="rowheader" cell with ISO-8601 week number, rendered at week row start when weekNumbers is set. rowheader rather than gridcell because the number labels its row.',props:[{name:"date",type:"Date | string",required:!0,description:`Any day of the week to number \u2014 the row's first cell is what GridBody passes. ${e}`}]},{name:"WeekNumberHeader",description:'role="columnheader" above the WeekNumber column, rendered when weekNumbers is set. Visually blank, named via aria-label.'},{name:"MonthGrid",lightDom:!0,description:`role="grid" container: twelve gridcells (three rows of four), one per month of focused year. Shows while view is 'month'. Full grid with accessible name, roving tabindex, arrow/Home/End/PageUp/PageDown at month unit. Unavailable months are aria-disabled="true", keeping them in roving order. ${t}`,props:[{name:"label",type:"string",required:!1,frameworks:["angular","webComponents"],exception:"attribute-passthrough",frameworksReason:"React, Vue and Svelte take `aria-label` on the element through attribute passthrough.",description:"Overrides the grid's accessible name, the locale-formatted month page by default."}]},{name:"YearGrid",lightDom:!0,description:`role="grid" container: twenty gridcells (five rows of four), one twenty-year page. Shows while view is 'year'. Same full grid model and aria-disabled rule as MonthGrid, one unit coarser. ${t}`,props:[{name:"label",type:"string",required:!1,frameworks:["angular","webComponents"],exception:"attribute-passthrough",frameworksReason:"React, Vue and Svelte take `aria-label` on the element through attribute passthrough.",description:"Overrides the grid's accessible name, the locale-formatted year page by default."}]},{name:"PreviousButton",description:'Button paging the calendar back one unit of the current view, named "Previous month", "Previous year" or "Previous 20 years" by default.',props:[{name:"label",type:"string",required:!1,frameworks:["angular","webComponents"],exception:"attribute-passthrough",frameworksReason:"React, Vue and Svelte take `aria-label` on the element through attribute passthrough.",description:"Accessible name for the button, replacing the view-dependent default."}]},{name:"NextButton",description:'Button paging the calendar forward one unit of the current view, named "Next month", "Next year" or "Next 20 years" by default.',props:[{name:"label",type:"string",required:!1,frameworks:["angular","webComponents"],exception:"attribute-passthrough",frameworksReason:"React, Vue and Svelte take `aria-label` on the element through attribute passthrough.",description:"Accessible name for the button, replacing the view-dependent default."}]},{name:"Heading",description:"Label for what is on screen and view-up trigger. Renders as button stepping view up when below maxView, otherwise plain label.",props:[{name:"monthOffset",type:"number",defaultValue:"0",required:!1,description:"Which of the displayed months to label, 0 being the first."}]},{name:"Cell",description:'role="gridcell" date button carrying state hooks (selected, today, outside-month, disabled, unavailable, data-range-*).',props:[{name:"date",type:"Date | string",required:!0,description:`The day this cell renders. Required and undefaulted: a cell has no meaning without one, and there is no sensible fallback \u2014 the grid supplies it while iterating a week, so it is the one prop every consumer writes. ${e}`}]}],slots:[{name:"default",description:"Replaces the derived anatomy entirely. Left empty, the calendar draws all of it. Takes the Calendar parts."},{name:"heading",description:"Displayed month's label; empty for locale-derived default."},{name:"cell",description:"A single selectable date cell."}],states:["default","selected","today","outside-month","disabled","unavailable","range-start","range-middle","range-end"],controlledUncontrolled:"value, month, focusedDate and view each work controlled or uncontrolled, independently of one another.",formBehavior:"Not applicable \u2014 Calendar does not itself submit; pair with DatePicker for a form-field trigger.",validationBehavior:"Not applicable \u2014 Calendar performs no validation beyond min/max/disabled/unavailable cell rendering. Range and multiple selections are announced through the shared LiveAnnouncer (the anchored start, then the completed range; the running count in multiple mode), because that state lives across several cells and no single aria-selected flip conveys it.",accessibility:{role:'grid (the date grid), rowgroup (GridHead and GridBody), row (each week and the weekday-name row), columnheader (each weekday name, and the week-number column when weekNumbers is on), rowheader (each week number), gridcell (each date). GridHead is a rowgroup, NOT a row, and GridBody is a rowgroup, not a bare wrapper \u2014 a role="row" nested in a role="row", or a plain div between the grid and its rows, is what axe caught as aria-required-children / aria-required-parent in all five frameworks; the implementations were fixed to match.',keyboard:[{key:"ArrowLeft/ArrowRight",behavior:"Move focus by one day."},{key:"ArrowUp/ArrowDown",behavior:"Move focus by one week."},{key:"Home/End",behavior:"Jump to the start/end of the focused week."},{key:"PageUp/PageDown",behavior:"Move focus by one month. Unlike the navigation buttons, which page by pageBehavior, the keyboard always moves a single month: it moves the focused *date*, and paging it by numberOfMonths would jump the cursor rather than the view."},{key:"Shift+PageUp/PageDown",behavior:"Move focus by one year."},{key:"ArrowLeft/ArrowRight (month/year grid)",behavior:"Move focus by one month, or by one year."},{key:"ArrowUp/ArrowDown (month/year grid)",behavior:"Move focus by one row \u2014 four months, or four years, since both grids are four columns wide."},{key:"Home/End (month/year grid)",behavior:"Jump to the first/last cell of the page: January/December, or the twenty-year page's first/last year."},{key:"PageUp/PageDown (month/year grid)",behavior:"Move focus by one whole page \u2014 a year in the month grid, twenty years in the year grid \u2014 and announce the new page, since paging replaces every cell and moves no focus assistive technology would report."},{key:"Shift+PageUp/PageDown (month/year grid)",behavior:"Move focus by ten pages \u2014 ten years, or two hundred."},{key:"Enter/Space (month/year grid)",behavior:"Navigate to the focused month or year and step the view down one level. Selects nothing: only the day grid selects."},{key:"Enter/Space",behavior:"Select the focused date (native button activation). In range mode the first press anchors the range start and the second closes it, ordering start/end so a backwards selection still yields start <= end; a third press starts over."}],ariaAttributes:["aria-selected on every gridcell in the selection \u2014 in range mode that is every date from start to end inclusive, not just the two endpoints",'aria-disabled="true" on an unavailable (but still focusable) gridcell',`aria-current="date" on the gridcell representing today, and on no other cell \u2014 the sighted-only data-today hook is not a substitute, because inside a grid of bare day numbers today is the one part of a cell's meaning that cannot be read off its own text`,"aria-multiselectable on the grid, false only in single-selection mode",'an accessible name on the grid naming its month, from the same Intl formatter the Heading uses \u2014 role="grid" is name-from-author only, so an unnamed one announces as a bare "grid", and with numberOfMonths above 1 there are several identical-sounding grids on screen. Emitted as aria-label and suppressed the moment the consumer supplies an aria-label or aria-labelledby of their own',`aria-label on every gridcell carrying the full localized date ("Monday, January 5, 2026"), from the same Intl formatter the selection announcement uses. A cell's own text is a bare day number, so an unlabelled cell announces as "14" with no month \u2014 which makes arrowing into an adjacent month, and a PageDown that changes it, completely silent. Localized by locale rather than by a prop, because it is derived from the value rather than invented by the widget`,'aria-readonly="true" on the grid while readOnly, and absent otherwise \u2014 a read-only calendar keeps every cell focusable and every tab stop, so this attribute is the only thing that distinguishes it from an editable one','aria-disabled="true" on the grid while the whole calendar is disabled, and absent otherwise \u2014 every cell is natively disabled and therefore silent, so the grid is the only element left to carry the state','an accessible name on MonthGrid and YearGrid too, for the same name-from-author reason the day grid needs one: the focused year ("2026") and the year page ("2020 - 2039"), from the same values the Heading renders, so the two never disagree about what is on screen',`aria-label on every month and year gridcell carrying the full localized unit name ("January 2026", "2026") \u2014 a month cell's own text is an abbreviated month name and a year cell's is four digits, neither of which says which year or which page it belongs to`,'aria-disabled="true" on a month or year gridcell no date can satisfy, which keeps it focusable rather than removing it from the grid \u2014 the unavailable model, not the disabled one, because a twelve-cell grid with silent gaps reads as a broken grid'],focusManagement:"A controlled value that moves to a date outside every displayed month takes the focused date and the displayed month with it, firing month-change \u2014 otherwise the grid keeps its only tab stop on a cell that is no longer rendered, and Tab reaches the calendar to land on nothing. Roving tabindex: only the currently focused date is a tab stop (tabIndex 0), all others -1, and that date is readable and settable as focusedDate with every move reported through focus-change. Unavailable dates keep their place in the roving order; disabled ones do not, and an outside-month day joins it only while outsideDaySelectable is on. A disabled calendar has no tab stop at all, because every cell carries the native disabled attribute. The month and year grids run the same roving model one unit coarser: the tab stop sits on the cell focusedDate falls in \u2014 its month, or its year \u2014 so the cursor survives every step up and down the view stack rather than resetting, and a cell no date can satisfy keeps its place in that order because it is aria-disabled rather than natively disabled.",namingRequirement:'Weekday columnheaders render the locale short name with the full locale name as aria-label, so screen readers say "Monday" rather than "Mon".'},styling:{cssParts:["calendar","header","heading","previous-button","next-button","grid","row","week-day","cell","grid-head","grid-body","month-grid","year-grid","view-cell","week-number","week-number-header"],dataAttributes:[...s,"data-state","data-today","data-outside-month","data-disabled","data-unavailable","data-range-start","data-range-middle","data-range-end","data-skdx-calendar-cell","data-skdx-calendar-view-cell",{name:"data-skdx-calendar-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:"setValue",signature:"(date: Date) => void",frameworks:["webComponents"],exception:"imperative",frameworksReason:"Only the custom element publishes it. The other four re-render with a new value 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:"Moves the selection to date and emits value-change, exactly as picking that cell would."}]};export{t as CALENDAR_PROJECTION_RULE,c as calendarContract};
@@ -1 +1 @@
1
- import{ROOT_DATA_ATTRIBUTES as e}from"./styling-hooks.js";var t={name:"PageContainer",purpose:"A page-level block composing Breadcrumb, Heading and Toolbar above the page content. Renders a <section> named by its own heading. Ships in the blocks package of each framework, not the core package.",props:[{name:"title",type:"string",required:!0,description:"The page heading text, rendered into a derived Heading that also names the section through aria-labelledby."},{name:"headingLevel",type:"1 | 2 | 3 | 4 | 5 | 6",defaultValue:"1",required:!1,description:"Heading level of the derived title. A page container normally holds the page's only <h1>; lower it when the block sits inside a larger outline."},{name:"breadcrumbs",type:"ReadonlyArray<{ label: string; href?: string; current?: boolean }>",required:!1,description:"Trail rendered into a derived Breadcrumb above the heading. No breadcrumb region renders when omitted."},{name:"breadcrumbLabel",type:"string",defaultValue:"'Breadcrumb'",required:!1,description:"Accessible name of the derived breadcrumb <nav>."},{name:"actionsLabel",type:"string",defaultValue:"'Page actions'",required:!1,description:"Accessible name of the derived actions Toolbar."},{name:"actions",type:"unknown",required:!1,frameworks:["react","svelte","angular"],exception:"render-prop",frameworksReason:"Vue and Web Components take this as a named slot rather than a prop \u2014 Vue because a renderable region is a slot in Vue's own idiom, Web Components because a custom element attribute carries a string and cannot carry a node. Both slot spellings are declared under `slots`.",description:"Controls rendered inside the derived Toolbar beside the heading. No toolbar renders when omitted. A prop in React, Svelte, and Angular; Vue and Web Components use the `actions` slot."}],events:[],slots:[{name:"default",description:"The page content below the header."},{name:"actions",description:"Controls placed in the toolbar beside the heading."}],states:["default"],controlledUncontrolled:"Stateless \u2014 PageContainer holds no internal state.",formBehavior:"Not applicable \u2014 PageContainer participates in no form.",validationBehavior:"Not applicable \u2014 PageContainer carries no value and participates in no validation.",accessibility:{role:"region (native <section> named by its heading through aria-labelledby)",keyboard:[{key:"ArrowLeft/ArrowRight",behavior:"Moves between the action controls inside the derived Toolbar, which is one Tab stop."}],ariaAttributes:["aria-labelledby on the <section> points at the derived heading, so the region is announced by the page title","the derived Breadcrumb is a <nav> named by breadcrumbLabel; the derived Toolbar is named by actionsLabel"],focusManagement:"Nothing inside the block is focusable except the breadcrumb links and the action controls the consumer supplies; the Toolbar applies its roving tabindex to them."},styling:{cssParts:["page-container","header","breadcrumbs","heading-row","title","actions","content"],dataAttributes:[...e,{name:"data-skdx-page-container-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{t as pageContainerContract};
1
+ import{ROOT_DATA_ATTRIBUTES as e}from"./styling-hooks.js";var t={name:"PageContainer",purpose:"A page-level block composing Breadcrumb, Heading and Toolbar above the page content. Renders a <section> named by its own heading. Ships in the blocks package of each framework, not the core package.",props:[{name:"title",type:"string",required:!0,description:"The page heading text, rendered into a derived Heading that also names the section through aria-labelledby."},{name:"headingLevel",type:"1 | 2 | 3 | 4 | 5 | 6",defaultValue:"1",required:!1,description:"Heading level of the derived title. A page container normally holds the page's only <h1>; lower it when the block sits inside a larger outline."},{name:"breadcrumbs",type:"ReadonlyArray<{ label: string; href?: string; current?: boolean }>",required:!1,description:"Trail rendered into a derived Breadcrumb above the heading. No breadcrumb region renders when omitted."},{name:"breadcrumbLabel",type:"string",defaultValue:"'Breadcrumb'",required:!1,description:"Accessible name of the derived breadcrumb <nav>."},{name:"actionsLabel",type:"string",defaultValue:"'Page actions'",required:!1,description:"Accessible name of the derived actions Toolbar."},{name:"actions",type:"unknown",required:!1,frameworks:["react","svelte","angular"],exception:"render-prop",frameworksReason:"Vue and Web Components take this as a named slot rather than a prop \u2014 Vue because a renderable region is a slot in Vue's own idiom, Web Components because a custom element attribute carries a string and cannot carry a node. Both slot spellings are declared under `slots`.",description:"Toolbar parts rendered inside the derived Toolbar beside the heading \u2014 a toolbar button, link, separator or toggle group. Anything else stays its own Tab stop and the arrow keys skip it, because only a toolbar part joins the roving tabindex. No toolbar renders when omitted. A prop in React, Svelte, and Angular; Vue and Web Components use the `actions` slot."}],events:[],slots:[{name:"default",description:"The page content below the header."},{name:"actions",description:"Toolbar parts placed in the toolbar beside the heading; a control that is not a toolbar part stays its own Tab stop."}],states:["default"],controlledUncontrolled:"Stateless \u2014 PageContainer holds no internal state.",formBehavior:"Not applicable \u2014 PageContainer participates in no form.",validationBehavior:"Not applicable \u2014 PageContainer carries no value and participates in no validation.",accessibility:{role:"region (native <section> named by its heading through aria-labelledby)",keyboard:[{key:"ArrowLeft/ArrowRight",behavior:"Moves between the toolbar parts supplied as actions, which together are one Tab stop; a control that is not a toolbar part is skipped and keeps its own Tab stop."}],ariaAttributes:["aria-labelledby on the <section> points at the derived heading, so the region is announced by the page title","the derived Breadcrumb is a <nav> named by breadcrumbLabel; the derived Toolbar is named by actionsLabel"],focusManagement:"Nothing inside the block is focusable except the breadcrumb links and the action controls the consumer supplies; the Toolbar applies its roving tabindex to the toolbar parts among them."},styling:{cssParts:["page-container","header","breadcrumbs","heading-row","title","actions","content"],dataAttributes:[...e,{name:"data-skdx-page-container-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{t as pageContainerContract};
@@ -1 +1 @@
1
- import{ROOT_DATA_ATTRIBUTES as e}from"./styling-hooks.js";var r={name:"SignUpPage",purpose:"A registration block composing Heading, Alert, Button, Divider, Form, Field, TextField, PasswordInput and Checkbox. Every provider renders a button and the credentials form always renders, so the block reports either a chosen provider or the submitted account details through one sign-up event. Ships in the blocks package of each framework, not the core package.",props:[{name:"title",type:"string",defaultValue:"'Create account'",required:!1,description:"Heading text, rendered into a derived <h1> that names the section."},{name:"providers",type:"ReadonlyArray<{ id: string; name: string }>",required:!1,description:"Third-party providers, in render order, each rendering one button above the credentials form. No provider region and no divider render when omitted."},{name:"error",type:"string",required:!1,description:"Message rendered into a derived error Alert above the form. The block never clears it: set it from the sign-up handler and reset it when the next attempt starts."},{name:"pending",type:"boolean",defaultValue:"false",required:!1,description:"Marks the request in flight, making the submit button busy and unusable while every field stays editable, so the user can still correct a typo."},{name:"nameLabel",type:"string",required:!1,description:"Label of the name field; supplying it is what makes that field render at all, and what adds name to the event detail."},{name:"emailLabel",type:"string",defaultValue:"'Email'",required:!1,description:"Label of the email field in the credentials form."},{name:"passwordLabel",type:"string",defaultValue:"'Password'",required:!1,description:"Label of the password field in the credentials form."},{name:"confirmPasswordLabel",type:"string",required:!1,description:"Label of the confirmation field; supplying it is what makes that field render and what turns on password matching."},{name:"passwordMismatchMessage",type:"string",defaultValue:"'Passwords do not match'",required:!1,description:"Validation message pushed onto the confirmation field as a custom validity when the two passwords differ."},{name:"terms",type:"unknown",required:!1,frameworks:["react","svelte","angular"],exception:"render-prop",frameworksReason:"Vue and Web Components take this as a named slot rather than a prop \u2014 Vue because a renderable region is a slot in Vue's own idiom, Web Components because a custom element attribute carries a string and cannot carry a node. Both slot spellings are declared under `slots`.",description:"Terms copy and links, rendered as the label of a required checkbox before the submit button. No terms field renders when omitted. A prop in React, Svelte, and Angular; Vue and Web Components use the `terms` slot."},{name:"submitLabel",type:"string",defaultValue:"'Create account'",required:!1,description:"Text of the credentials form submit button."},{name:"getProviderLabel",type:"(provider: { id: string; name: string }) => string",defaultValue:"defaultProviderLabel",required:!1,description:`Formats a provider button's text from its entry. The default reads "Sign in with Google".`},{name:"footer",type:"unknown",required:!1,frameworks:["react","svelte","angular"],exception:"render-prop",frameworksReason:"Vue and Web Components take this as a named slot rather than a prop \u2014 Vue because a renderable region is a slot in Vue's own idiom, Web Components because a custom element attribute carries a string and cannot carry a node. Both slot spellings are declared under `slots`.",description:"Navigation rendered below the form, such as a link back to the sign-in page. No footer region renders when omitted. A prop in React, Svelte, and Angular; Vue and Web Components use the `footer` slot."}],events:[{name:"sign-up",detail:"{ providerId: string; name?: string; email?: string; password?: string; termsAccepted?: boolean }",description:"Fires when a provider button is activated or the form is submitted with matching, valid fields. A button carries only providerId; the form carries 'credentials' plus the values of the fields that rendered. The native submit is prevented, so the consumer performs the request and sets error on failure."}],slots:[{name:"default",description:"Extra fields rendered inside the form, above the terms and the submit button."},{name:"terms",description:"Terms copy and links used as the label of the required terms checkbox; nothing renders without it."},{name:"footer",description:"Navigation below the form, such as a link back to the sign-in page."}],states:["default"],controlledUncontrolled:"Stateless apart from the field values the native inputs hold; error, pending and success wording are always controlled by the consumer.",formBehavior:"The credentials form is a core Form root with required, natively validated fields; an invalid form reports through constraint validation and emits nothing. The submit button is disabled while pending, and the fields stay editable.",validationBehavior:'Fields are required, the email field is type="email", and the terms checkbox must be ticked. When a confirmation field renders, a mismatch is pushed onto it as a custom validity, reported through the form, and cleared on the next edit of that field.',accessibility:{role:"region (native <section> named by its heading through aria-labelledby)",keyboard:[{key:"Enter",behavior:"Submits the form whose field has focus (native form submission)."},{key:"Enter/Space",behavior:"Activates a focused provider button (native button activation)."},{key:"Space",behavior:"Toggles the terms checkbox when it has focus."}],ariaAttributes:["aria-labelledby on the <section> points at the derived heading","the derived error Alert is a live region, so a new error is announced without moving focus","each field is labelled through the core Field, which wires aria-describedby and aria-invalid"],focusManagement:"Focus stays where the user put it, except that a reported password mismatch moves focus to the confirmation field the way constraint validation does."},styling:{cssParts:["sign-up-page","panel","title","error","providers","provider","divider","form","field","terms","submit","footer"],dataAttributes:[...e,"data-provider",{name:"data-skdx-sign-up-page-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.'},{name:"data-skdx-sign-up-page-content",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{r as signUpPageContract};
1
+ import{ROOT_DATA_ATTRIBUTES as e}from"./styling-hooks.js";var r={name:"SignUpPage",purpose:"A registration block composing Heading, Alert, Button, Divider, Form, Field, TextField, PasswordInput and Checkbox. Every provider renders a button and the credentials form always renders, so the block reports either a chosen provider or the submitted account details through one sign-up event. Ships in the blocks package of each framework, not the core package.",props:[{name:"title",type:"string",defaultValue:"'Create account'",required:!1,description:"Heading text, rendered into a derived <h1> that names the section."},{name:"providers",type:"ReadonlyArray<{ id: string; name: string }>",required:!1,description:"Third-party providers, in render order, each rendering one button above the credentials form. No provider region and no divider render when omitted."},{name:"error",type:"string",required:!1,description:"Message rendered into a derived error Alert above the form. The block never clears it: set it from the sign-up handler and reset it when the next attempt starts."},{name:"pending",type:"boolean",defaultValue:"false",required:!1,description:"Marks the request in flight, making the submit button busy and unusable while every field stays editable, so the user can still correct a typo."},{name:"nameLabel",type:"string",required:!1,description:"Label of the name field; supplying it is what makes that field render at all, and what adds name to the event detail."},{name:"emailLabel",type:"string",defaultValue:"'Email'",required:!1,description:"Label of the email field in the credentials form."},{name:"passwordLabel",type:"string",defaultValue:"'Password'",required:!1,description:"Label of the password field in the credentials form."},{name:"confirmPasswordLabel",type:"string",required:!1,description:"Label of the confirmation field; supplying it is what makes that field render and what turns on password matching."},{name:"passwordMismatchMessage",type:"string",defaultValue:"'Passwords do not match'",required:!1,description:"Validation message pushed onto the confirmation field as a custom validity when the two passwords differ."},{name:"terms",type:"unknown",required:!1,frameworks:["react","svelte","angular"],exception:"render-prop",frameworksReason:"Vue and Web Components take this as a named slot rather than a prop \u2014 Vue because a renderable region is a slot in Vue's own idiom, Web Components because a custom element attribute carries a string and cannot carry a node. Both slot spellings are declared under `slots`.",description:"Terms copy and links, rendered as the label of a required checkbox before the submit button. No terms field renders when omitted. A prop in React, Svelte, and Angular; Vue and Web Components use the `terms` slot."},{name:"submitLabel",type:"string",defaultValue:"'Create account'",required:!1,description:"Text of the credentials form submit button."},{name:"getProviderLabel",type:"(provider: { id: string; name: string }) => string",defaultValue:"defaultProviderLabel",required:!1,description:`Formats a provider button's text from its entry. The default reads "Sign in with Google".`},{name:"footer",type:"unknown",required:!1,frameworks:["react","svelte","angular"],exception:"render-prop",frameworksReason:"Vue and Web Components take this as a named slot rather than a prop \u2014 Vue because a renderable region is a slot in Vue's own idiom, Web Components because a custom element attribute carries a string and cannot carry a node. Both slot spellings are declared under `slots`.",description:"Navigation rendered below the form, such as a link back to the sign-in page. No footer region renders when omitted. A prop in React, Svelte, and Angular; Vue and Web Components use the `footer` slot."}],events:[{name:"sign-up",detail:"{ providerId: string; name?: string; email?: string; password?: string; termsAccepted?: boolean }",description:"Fires when a provider button is activated or the form is submitted with matching, valid fields. A button carries only providerId; the form carries 'credentials' plus the values of the fields that rendered. The native submit is prevented, so the consumer performs the request and sets error on failure."}],slots:[{name:"default",description:"Extra fields rendered inside the form, above the terms and the submit button."},{name:"terms",description:"Terms copy and links used as the label of the required terms checkbox; nothing renders without it."},{name:"footer",description:"Navigation below the form, such as a link back to the sign-in page."}],states:["default"],controlledUncontrolled:"Stateless apart from the field values the native inputs hold; error, pending and success wording are always controlled by the consumer.",formBehavior:"The credentials form is a core Form root with required, natively validated fields; an invalid form reports through constraint validation and emits nothing. The submit button is disabled while pending, and the fields stay editable.",validationBehavior:`Fields are required, the email field is type="email", and the terms checkbox must be ticked. A refused terms box is reported on the checkbox itself \u2014 aria-invalid on the control and the browser's own wording in the field's error part \u2014 since the constraint lives on a hidden mirror input no message can be anchored to; ticking the box clears it. When a confirmation field renders, a mismatch is pushed onto it as a custom validity, reported through the form, and cleared on the next edit of that field.`,accessibility:{role:"region (native <section> named by its heading through aria-labelledby)",keyboard:[{key:"Enter",behavior:"Submits the form whose field has focus (native form submission)."},{key:"Enter/Space",behavior:"Activates a focused provider button (native button activation)."},{key:"Space",behavior:"Toggles the terms checkbox when it has focus."}],ariaAttributes:["aria-labelledby on the <section> points at the derived heading","the derived error Alert is a live region, so a new error is announced without moving focus","each field is labelled through the core Field, which wires aria-describedby and aria-invalid"],focusManagement:"Focus stays where the user put it, except that a reported password mismatch moves focus to the confirmation field the way constraint validation does."},styling:{cssParts:["sign-up-page","panel","title","error","providers","provider","divider","form","field","terms","submit","footer"],dataAttributes:[...e,"data-provider",{name:"data-skdx-sign-up-page-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.'},{name:"data-skdx-sign-up-page-content",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{r as signUpPageContract};
@@ -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.'},{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 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 +1 @@
1
- import{ROOT_DATA_ATTRIBUTES as e}from"./styling-hooks.js";var a={name:"Tree",purpose:"A hierarchical list following the WAI-ARIA treeview pattern. The root owns expanded/selected/active-node state; nodes with a nested TreeGroup are expandable.",props:[{name:"selectionMode",type:"'single' | 'multiple' | 'none'",defaultValue:"'single'",required:!1,description:'"none" makes the tree expand/collapse-only, with no node selection. "single" means at most one id on each axis \u2014 `selectedIds` and `checkedIds` hold one entry or none \u2014 because a checked set left to derive from selection can never outgrow it. "multiple" is what lifts that cap, and the only mode selectionPropagation means anything in.'},{name:"expandedIds",type:"string[]",required:!1,description:"Expanded node ids, for controlled usage."},{name:"defaultExpandedIds",type:"string[]",defaultValue:"[]",required:!1,description:"Initially expanded node ids, for uncontrolled usage."},{name:"selectedIds",type:"string[]",required:!1,description:"Selected node ids, for controlled usage."},{name:"defaultSelectedIds",type:"string[]",defaultValue:"[]",required:!1,description:"Initially selected node ids, for uncontrolled usage."},{name:"checkedIds",type:"string[]",required:!1,description:'Checked node ids, for controlled usage. A second axis from `selectedIds`, which means row highlight only \u2014 a tri-state checkbox tree has to be able to say "highlighted but unticked", and one set cannot. Omit it and `aria-checked` keeps deriving from `selectedIds` exactly as before, so an existing tree is unaffected; supply it and the two come apart.'},{name:"defaultCheckedIds",type:"string[]",required:!1,description:"Initially checked node ids, for uncontrolled usage. Omit it, like `checkedIds`, to keep checked state derived from selection."},{name:"selectionPropagation",type:"'none' | 'descendants'",defaultValue:"'none'",required:!1,description:'Turns the tree into a tri-state checkbox tree: toggling a node applies to its whole subtree. Only meaningful with selectionMode="multiple". Propagation reaches every descendant the tree has ever rendered, not only the mounted ones. A collapsed group is unmounted, so its parent stays reporting "mixed" rather than silently dropping to "false". A branch whose children have never been mounted \u2014 declared through hasChildren before loadChildren runs \u2014 reports its own selected state until first expanded.'},{name:"loadChildren",type:"(nodeId: string) => void | Promise<void>",required:!1,description:`Loads a node's children the first time it is expanded. The node carries aria-busy="true" and data-loading until the returned promise settles; on resolution getLoadedLabel is announced through the live region. A rejection clears the busy state and leaves the node eligible to retry. The node has to be declared a branch through the item's hasChildren to be expandable before anything is loaded, since nothing has been rendered under it yet.`},{name:"getLoadedLabel",type:"(nodeId: string) => string",defaultValue:"defaultGetLoadedLabel",required:!1,description:`Formats the message announced when a lazily-loaded node's children arrive. Return an empty string to announce nothing. The default announces the node's own id followed by "loaded", e.g. "src loaded".`},{name:"disabledBehavior",type:"'all' | 'selection'",defaultValue:"'all'",required:!1,description:`What an item's disabled takes away. 'all' makes the node inert: arrows step over it. 'selection' keeps it in the roving-focus order and typeahead list, refusing selection only, which is the only way its aria-disabled="true" is heard. Choose 'selection' when the reason a node is disabled is itself information the user needs.`},{name:"collection",type:"ReadonlyArray<{ nodeId: string; label?: string; disabled?: boolean; hasChildren?: boolean; textValue?: string; children?: TreeNode[] }>",required:!1,description:'Hierarchical data the tree renders instead of TreeItem/TreeGroup children. The source of truth when supplied; children slot is ignored. Node ids must be unique across the collection. A node without children is a leaf unless hasChildren declares it a branch whose children are still unloaded (aria-expanded="false"). The rendered structure is identical to hand-written composition. This is the same shape TreeSelect takes, spelled with nodeId rather than value.'},{name:"renderLabel",type:"(node: TreeNode) => 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. Declared exactly as the sibling member on TreeSelect's Tree part is, and for the same reason: the gate matches member names literally and has no per-prop rename mechanism.",description:"Renders a collection node's content \u2014 label and any indicator. Ignored without collection, since composition already puts the consumer in charge. Defaults to the node's label, falling back to its nodeId. In Angular this is `labelTemplate`, a TemplateRef taking the same context."},{name:"expandOnClick",type:"boolean",defaultValue:"true",required:!1,description:"Whether clicking a branch row toggles its expansion as well as selecting it. false leaves expansion to the item indicator chevron alone. Keyboard ArrowRight/ArrowLeft are unaffected."}],events:[{name:"expanded-ids-change",description:"Fires with the new set of expanded ids.",detail:"string[]"},{name:"selected-ids-change",description:"Fires with the new set of selected ids.",detail:"string[]"},{name:"checked-ids-change",detail:"string[]",description:"Fires with the new set of checked ids. Only fires once the tree has a checked axis of its own \u2014 that is, when `checkedIds` or `defaultCheckedIds` is supplied."},{name:"activate",description:'Fires with the node id when a node is activated \u2014 Enter, or a pointer click on the row. Activation is not selection: APG separates Enter ("performs its default action") from Space ("toggles the selection state"), and a file tree needs both, one to open the file and one to tick it. Space never fires this. Selection still happens on both keys, so the two axes stay independent rather than one replacing the other.',detail:"string"},{name:"load-error",description:"Fires with { nodeId, error } when a node's loadChildren promise rejects. Without it a failed load is indistinguishable from an empty branch: the busy flag clears, nothing is announced, and the node sits expanded with no children and no explanation. The rejection is not rethrown \u2014 a floating unhandled rejection would be a worse failure mode \u2014 so this is the only place a retry affordance or a failure announcement can come from. The node is left eligible to retry.",detail:"{ nodeId: string; error: unknown }"}],parts:[{name:"Item",description:'One role="treeitem" node, carrying aria-level, aria-posinset/aria-setsize, and \u2014 on a branch \u2014 aria-expanded and data-state.',props:[{name:"nodeId",type:"string",required:!0,description:"This node's identity in every piece of state the root owns \u2014 expandedIds, selectedIds, the active (tabbable) node, the loading set and the parent/child hierarchy the tri-state derives from \u2014 and the value published on data-skdx-tree-node-id. Required rather than defaulted: an id the tree invented would change on every remount, so nothing controlled could ever address the node."},{name:"disabled",type:"boolean",defaultValue:"false",required:!1,description:`Excludes this node from selection and keyboard activation. Under the root's default disabledBehavior="all", takes it out of the roving-focus order so arrows step over it. It stays announced as aria-disabled="true", explaining why focus never arrives. Not inherited: the node is still rendered, its subtree still expands, and its descendants stay fully navigable and selectable.`},{name:"textValue",type:"string",required:!1,description:"The text typeahead matches this node against, instead of its rendered label, published as data-text-value. Use it when the label is not plain text \u2014 an icon plus a truncated name, a highlighted filter match \u2014 or when the searchable text differs from the visible one. Omitted, the node's own text is used, excluding its aria-hidden indicator and its nested group."},{name:"hasChildren",type:"boolean",required:!1,description:"Declares whether this node is a branch, overriding the nested-group scan. true makes the node report aria-expanded and accept ArrowRight with no group mounted at all, which a lazily-loaded branch needs. false forces the node to read as a leaf even with a group among its children. Inferred from the presence of a nested group when omitted."}]},{name:"Group",description:`One role="group" container for a branch's child items, rendered inside the TreeItem it belongs to. It is unmounted while its branch is collapsed rather than hidden, which is what makes the roving tabindex span currently-visible items for free.`,props:[{name:"forceMount",type:"boolean",defaultValue:"false",required:!1,description:"Keeps the group mounted while its branch is collapsed, for exit animations. The consumer owns hiding it. A force-mounted group's items stay registered, counting towards their parent's tri-state and reachable by roving focus."}]},{name:"ItemIndicator",description:"A branch's expand/collapse affordance, rendered inside the TreeItem it belongs to. It mirrors its item's data-state (expanded/collapsed) so the skin can rotate one glyph rather than swap two, and clicking it toggles the branch without selecting the row. Decorative to assistive technology: the item's own aria-expanded already carries the state. A collection tree derives one for every branch, so a hand-composed tree and a collection tree have the same affordance.",props:[]}],slots:[{name:"default",description:"Root content \u2014 TreeItems. Ignored while `collection` is supplied, which is then the source of truth."},{name:"indicator",description:"A TreeItem's expand/collapse affordance (e.g. a chevron)."}],states:["expanded","collapsed","selected","unselected","loading","indeterminate"],controlledUncontrolled:"Expansion and selection each support both controlled and uncontrolled usage, independently of one another.",formBehavior:"Not applicable \u2014 Tree does not participate in forms. Angular alone additionally registers as a ControlValueAccessor, which is an extension of that framework rather than a capability the other four owe; see frameworkExtensions.",validationBehavior:"Not applicable \u2014 Tree carries no value and participates in no validation.",accessibility:{role:"tree (root), treeitem (TreeItem), group (TreeGroup)",keyboard:[{key:"ArrowUp/ArrowDown",behavior:"Moves focus between currently-visible items, no wrap."},{key:"Home/End",behavior:"Moves focus to the first/last currently-visible item."},{key:"ArrowRight",behavior:"Expands a collapsed branch, or moves focus to its first child if already expanded. A node declared a branch through the item's hasChildren counts as one with nothing rendered under it yet, which is what lets ArrowRight trigger loadChildren."},{key:"ArrowLeft",behavior:"Collapses an expanded node with children, or moves focus to its parent."},{key:"Printable characters",behavior:"Typeahead: moves focus to the next currently-visible node whose label starts with what was typed, wrapping around. A 1s buffer accumulates characters into one search; repeating a single letter cycles through the nodes starting with it. A node's label is its `data-text-value` when it has one, otherwise its own text \u2014 excluding its aria-hidden TreeItemIndicator and any nested TreeGroup, so a chevron glyph and a child's text never enter the match. Typeahead moves focus only, it never selects; Space is claimed by selection, so it never starts a search."},{key:"*",behavior:"Expands every collapsed sibling of the focused node \u2014 the nodes at the same level under the same parent. Focus does not move."},{key:"Shift+ArrowUp/Shift+ArrowDown",behavior:`selectionMode="multiple" only: moves focus to the previous/next visible node and toggles that node's selection, extending the selection as focus travels. Plain ArrowUp/ArrowDown elsewhere.`},{key:"Shift+Space",behavior:'selectionMode="multiple" only: selects the contiguous run of visible nodes between the most recently *selected* node and the focused one, inclusive, in one update. The anchor is the last node selected, not the last node focused, so walking with plain arrows and then pressing Shift+Space extends from wherever the selection last landed \u2014 which is the point of the binding. With no anchor yet (or one that has since been collapsed away) it selects the focused node alone, exactly as Space would.'},{key:"Ctrl+ArrowUp/Ctrl+ArrowDown",behavior:"Identical to plain ArrowUp/ArrowDown: moves focus to the previous/next visible node without touching the selection. There is no separate handler, and none is needed \u2014 selection never follows focus in this tree, so the plain arrows already do what the modifier asks for. It is listed because a user arriving from APG's alternative multi-select model, where the plain arrows *do* move the selection, will reach for it; and because a Control-modified arrow must not start a typeahead search, which is the one thing that would otherwise differ."},{key:"Control+Shift+Home/Control+Shift+End",behavior:`selectionMode="multiple" only: moves focus to the first/last visible node and selects every node from the previously focused one through it. This is APG's binding. The bare Shift+Home/Shift+End form is kept as an alias for one release \u2014 it is what the component shipped with \u2014 but is not the documented one. Plain Home/End moves focus only.`},{key:"Ctrl+A",behavior:'selectionMode="multiple" only: selects every currently-visible node, or clears the selection when every one of them is already selected. Focus does not move. The toggle matters because the same chord is the only affordance a user has for undoing it. The scope is the *rendered* tree rather than the whole tree, which is as far as it can reach: a collapsed branch is unmounted, and the root holds no list of what is inside one.'},{key:"Enter",behavior:'Selects the focused node *and* fires the activate event. APG: "Activates a node, i.e., performs its default action."'},{key:"Space",behavior:'Selects the focused node and nothing else \u2014 never fires activate. APG: "Toggles the selection state of the focused node."'}],ariaAttributes:['aria-label or aria-labelledby on the root, supplied by the consumer \u2014 normative for role="tree" (see `namingRequirement`)','aria-multiselectable on the root when selectionMode is "multiple"',"each item's aria-level reflects its nesting depth","aria-setsize and aria-posinset (1-based) on every item, counting its siblings at the same level under the same parent \u2014 required rather than optional here because collapsed subtrees are unmounted, so assistive tech cannot infer a set it cannot see","author-supplied aria-level, aria-posinset and aria-setsize override the computed ones. This is contracted, not incidental: it is what the virtualization pattern depends on \u2014 a windowed tree renders a slice of the nodes and has to state the real positions itself \u2014 and it is exactly the case APG requires the attributes for. Every framework spreads consumer attributes after its own, and a Tree item given an explicit aria-posinset must render that value",`the computed pair is measured from the rendered DOM after mount, so server-rendered HTML carries neither. A non-hydrated tree \u2014 an Astro island, an RSC subtree \u2014 must supply them itself through the override above, which is the same instruction the virtualization pattern already gives. There is no equivalent of the roving tab stop's "every item is a tab stop until the effect runs" fallback here: a guessed position is a wrong position, and a wrong "3 of 7" is worse than none`,'aria-selected reflects selection unless selectionMode is "none" \u2014 or selectionPropagation is "descendants", where aria-checked carries the state instead and aria-selected is omitted entirely',"aria-expanded is set only on branch items \u2014 those with a nested TreeGroup, plus those declared as branches through the item's hasChildren, which is how a lazy branch announces itself before its children exist",'aria-busy="true" on an item whose loadChildren call is still pending','aria-checked (true/false/"mixed") on every item when selectionPropagation is "descendants" \u2014 never alongside aria-selected: APG allows either, and on a parent whose children are partly selected the two contradict each other ("false" and "mixed" at once)'],namingRequirement:`The root must carry an accessible name via aria-label or aria-labelledby, supplied by the consumer. This is normative rather than advisory \u2014 APG: "The element with role tree has either a visible label referenced by aria-labelledby or a value specified for aria-label." No prop is needed, because passthrough already carries both attributes in all five packages; and nothing inside the Tree can supply it, since role="tree" is name-from-author only and the node labels inside it name the tree no more than a list's items name the list. An unnamed tree announces as a bare "tree", which is indistinguishable from every other tree on the page.`,focusManagement:'Single tab stop with roving tabindex over currently-visible items only \u2014 collapsed subtrees are unmounted, not merely hidden, so they are naturally excluded (under disabledBehavior="selection" the disabled ones stay in). The stop is seeded from the first rendered id present in selectedIds, falling back to the first rendered id \u2014 APG: "If a node is selected before the tree receives focus, focus is set on the selected node", and for a multi-select tree "focus is set on the first selected node". It is re-seeded by the same rule whenever the item holding it is removed, so collapsing or unmounting the focused branch cannot leave the tree with no tabbable item. Only the innermost treeitem under the pointer acts on a click: items nest, so a click on a leaf also reaches every ancestor handler, which would otherwise select and collapse the whole branch above it.'},styling:{cssParts:["root","item","group","item-indicator"],dataAttributes:[...e,"data-state","data-disabled","data-loading","data-checked","data-text-value","data-skdx-tree-node-id","data-skdx-tree-parent-id"],cssCustomProperties:["--skdx-tree-node-depth"]},publicMethods:[{name:"invalidate",signature:"(nodeId?: string): void",description:"Clears the loaded marker on a node so the next expansion fetches again. With nodeId, clears that node and every child it has ever rendered; omitted, clears the whole tree. The consumer owns fetching and caching."}],frameworkExtensions:[{framework:"angular",name:"ControlValueAccessor",type:"ControlValueAccessor",description:"Registers the tree as a Reactive Forms control so [(ngModel)] and formControlName carry the selection. It adds one behaviour no other package has: setDisabledState() disables the whole tree, emitting aria-disabled and data-disabled on the root and refusing selection changes, select-many and select-all. Angular-only because Reactive Forms exists nowhere else \u2014 the other four reach the same place through the controlled `selectedIds` prop and its change event, which Tree already declares."}]};export{a as treeContract};
1
+ import{ROOT_DATA_ATTRIBUTES as e}from"./styling-hooks.js";var a={name:"Tree",purpose:"A hierarchical list following the WAI-ARIA treeview pattern. The root owns expanded/selected/active-node state; nodes with a nested TreeGroup are expandable.",props:[{name:"selectionMode",type:"'single' | 'multiple' | 'none'",defaultValue:"'single'",required:!1,description:'"none" makes the tree expand/collapse-only, with no node selection. "single" means at most one id on each axis \u2014 `selectedIds` and `checkedIds` hold one entry or none \u2014 because a checked set left to derive from selection can never outgrow it. "multiple" is what lifts that cap, and the only mode selectionPropagation means anything in.'},{name:"expandedIds",type:"string[]",required:!1,description:"Expanded node ids, for controlled usage."},{name:"defaultExpandedIds",type:"string[]",defaultValue:"[]",required:!1,description:"Initially expanded node ids, for uncontrolled usage."},{name:"selectedIds",type:"string[]",required:!1,description:"Selected node ids, for controlled usage."},{name:"defaultSelectedIds",type:"string[]",defaultValue:"[]",required:!1,description:"Initially selected node ids, for uncontrolled usage."},{name:"checkedIds",type:"string[]",required:!1,description:'Checked node ids, for controlled usage. A second axis from `selectedIds`, which means row highlight only \u2014 a tri-state checkbox tree has to be able to say "highlighted but unticked", and one set cannot. Omit it and `aria-checked` keeps deriving from `selectedIds` exactly as before, so an existing tree is unaffected; supply it and the two come apart.'},{name:"defaultCheckedIds",type:"string[]",required:!1,description:"Initially checked node ids, for uncontrolled usage. Omit it, like `checkedIds`, to keep checked state derived from selection."},{name:"selectionPropagation",type:"'none' | 'descendants'",defaultValue:"'none'",required:!1,description:'Turns the tree into a tri-state checkbox tree: toggling a node applies to its whole subtree. Only meaningful with selectionMode="multiple". Propagation reaches every descendant the tree has ever rendered, not only the mounted ones. A collapsed group is unmounted, so its parent stays reporting "mixed" rather than silently dropping to "false". A branch whose children have never been mounted \u2014 declared through hasChildren before loadChildren runs \u2014 reports its own selected state until first expanded.'},{name:"loadChildren",type:"(nodeId: string) => void | Promise<void>",required:!1,description:`Loads a node's children the first time it is expanded. The node carries aria-busy="true" and data-loading until the returned promise settles; on resolution getLoadedLabel is announced through the live region. A rejection clears the busy state and leaves the node eligible to retry. The node has to be declared a branch through the item's hasChildren to be expandable before anything is loaded, since nothing has been rendered under it yet.`},{name:"getLoadedLabel",type:"(nodeId: string) => string",defaultValue:"defaultGetLoadedLabel",required:!1,description:`Formats the message announced when a lazily-loaded node's children arrive. Return an empty string to announce nothing. The default announces the node's own id followed by "loaded", e.g. "src loaded".`},{name:"disabledBehavior",type:"'all' | 'selection'",defaultValue:"'all'",required:!1,description:`What an item's disabled takes away. 'all' makes the node inert: arrows step over it. 'selection' keeps it in the roving-focus order and typeahead list, refusing selection only, which is the only way its aria-disabled="true" is heard. Choose 'selection' when the reason a node is disabled is itself information the user needs.`},{name:"collection",type:"ReadonlyArray<{ nodeId: string; label?: string; disabled?: boolean; hasChildren?: boolean; textValue?: string; children?: TreeNode[] }>",required:!1,description:'Hierarchical data the tree renders instead of TreeItem/TreeGroup children. The source of truth when supplied; children slot is ignored. Node ids must be unique across the collection. A node without children is a leaf unless hasChildren declares it a branch whose children are still unloaded (aria-expanded="false"). The rendered structure is identical to hand-written composition. This is the same shape TreeSelect takes, spelled with nodeId rather than value.'},{name:"renderLabel",type:"(node: TreeNode) => 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. Declared exactly as the sibling member on TreeSelect's Tree part is, and for the same reason: the gate matches member names literally and has no per-prop rename mechanism.",description:"Renders a collection node's content \u2014 label and any indicator. Ignored without collection, since composition already puts the consumer in charge. Defaults to the node's label, falling back to its nodeId. In Angular this is `labelTemplate`, a TemplateRef taking the same context."},{name:"expandOnClick",type:"boolean",defaultValue:"true",required:!1,description:"Whether clicking a branch row toggles its expansion as well as selecting it. false leaves expansion to the item indicator chevron alone. Keyboard ArrowRight/ArrowLeft are unaffected."}],events:[{name:"expanded-ids-change",description:"Fires with the new set of expanded ids.",detail:"string[]"},{name:"selected-ids-change",description:"Fires with the new set of selected ids.",detail:"string[]"},{name:"checked-ids-change",detail:"string[]",description:"Fires with the new set of checked ids. Only fires once the tree has a checked axis of its own \u2014 that is, when `checkedIds` or `defaultCheckedIds` is supplied."},{name:"activate",description:'Fires with the node id when a node is activated \u2014 Enter, or a pointer click on the row. Activation is not selection: APG separates Enter ("performs its default action") from Space ("toggles the selection state"), and a file tree needs both, one to open the file and one to tick it. Space never fires this. Selection still happens on both keys, so the two axes stay independent rather than one replacing the other.',detail:"string"},{name:"load-error",description:"Fires with { nodeId, error } when a node's loadChildren promise rejects. Without it a failed load is indistinguishable from an empty branch: the busy flag clears, nothing is announced, and the node sits expanded with no children and no explanation. The rejection is not rethrown \u2014 a floating unhandled rejection would be a worse failure mode \u2014 so this is the only place a retry affordance or a failure announcement can come from. The node is left eligible to retry.",detail:"{ nodeId: string; error: unknown }"}],parts:[{name:"Item",description:'One role="treeitem" node, carrying aria-level, aria-posinset/aria-setsize, and \u2014 on a branch \u2014 aria-expanded and data-state.',props:[{name:"nodeId",type:"string",required:!0,description:"This node's identity in every piece of state the root owns \u2014 expandedIds, selectedIds, the active (tabbable) node, the loading set and the parent/child hierarchy the tri-state derives from \u2014 and the value published on data-skdx-tree-node-id. Required rather than defaulted: an id the tree invented would change on every remount, so nothing controlled could ever address the node."},{name:"disabled",type:"boolean",defaultValue:"false",required:!1,description:`Excludes this node from selection and keyboard activation. Under the root's default disabledBehavior="all", takes it out of the roving-focus order so arrows step over it. It stays announced as aria-disabled="true", explaining why focus never arrives. Not inherited: the node is still rendered, its subtree still expands, and its descendants stay fully navigable and selectable.`},{name:"textValue",type:"string",required:!1,description:"The text typeahead matches this node against, instead of its rendered label, published as data-text-value. Use it when the label is not plain text \u2014 an icon plus a truncated name, a highlighted filter match \u2014 or when the searchable text differs from the visible one. Omitted, the node's own text is used, excluding its aria-hidden indicator and its nested group."},{name:"hasChildren",type:"boolean",required:!1,description:"Declares whether this node is a branch, overriding the nested-group scan. true makes the node report aria-expanded and accept ArrowRight with no group mounted at all, which a lazily-loaded branch needs. false forces the node to read as a leaf even with a group among its children. Inferred from the presence of a nested group when omitted."}]},{name:"Group",description:`One role="group" container for a branch's child items, rendered inside the TreeItem it belongs to. It is unmounted while its branch is collapsed rather than hidden, which is what makes the roving tabindex span currently-visible items for free. Publishes data-state expanded/collapsed, the hook a force-mounted group is styled by while its branch is closed.`,props:[{name:"forceMount",type:"boolean",defaultValue:"false",required:!1,description:`Keeps the group mounted while its branch is collapsed, for exit animations. The skin then hides it on data-state="collapsed", since painting it would contradict the branch's aria-expanded="false"; override that rule to animate the exit. A force-mounted group's items stay registered, counting towards their parent's tri-state.`}]},{name:"ItemIndicator",description:"A branch's expand/collapse affordance, rendered inside the TreeItem it belongs to. It mirrors its item's data-state (expanded/collapsed) so the skin can rotate one glyph rather than swap two, and clicking it toggles the branch without selecting the row. Decorative to assistive technology: the item's own aria-expanded already carries the state. A collection tree derives one for every branch, so a hand-composed tree and a collection tree have the same affordance.",props:[]}],slots:[{name:"default",description:"Root content \u2014 TreeItems. Ignored while `collection` is supplied, which is then the source of truth."},{name:"indicator",description:"A TreeItem's expand/collapse affordance (e.g. a chevron)."}],states:["expanded","collapsed","selected","unselected","loading","indeterminate"],controlledUncontrolled:"Expansion and selection each support both controlled and uncontrolled usage, independently of one another.",formBehavior:"Not applicable \u2014 Tree does not participate in forms. Angular alone additionally registers as a ControlValueAccessor, which is an extension of that framework rather than a capability the other four owe; see frameworkExtensions.",validationBehavior:"Not applicable \u2014 Tree carries no value and participates in no validation.",accessibility:{role:"tree (root), treeitem (TreeItem), group (TreeGroup)",keyboard:[{key:"ArrowUp/ArrowDown",behavior:"Moves focus between currently-visible items, no wrap."},{key:"Home/End",behavior:"Moves focus to the first/last currently-visible item."},{key:"ArrowRight",behavior:"Expands a collapsed branch, or moves focus to its first child if already expanded. A node declared a branch through the item's hasChildren counts as one with nothing rendered under it yet, which is what lets ArrowRight trigger loadChildren."},{key:"ArrowLeft",behavior:"Collapses an expanded node with children, or moves focus to its parent."},{key:"Printable characters",behavior:"Typeahead: moves focus to the next currently-visible node whose label starts with what was typed, wrapping around. A 1s buffer accumulates characters into one search; repeating a single letter cycles through the nodes starting with it. A node's label is its `data-text-value` when it has one, otherwise its own text \u2014 excluding its aria-hidden TreeItemIndicator and any nested TreeGroup, so a chevron glyph and a child's text never enter the match. Typeahead moves focus only, it never selects; Space is claimed by selection, so it never starts a search."},{key:"*",behavior:"Expands every collapsed sibling of the focused node \u2014 the nodes at the same level under the same parent. Focus does not move."},{key:"Shift+ArrowUp/Shift+ArrowDown",behavior:`selectionMode="multiple" only: moves focus to the previous/next visible node and toggles that node's selection, extending the selection as focus travels. Plain ArrowUp/ArrowDown elsewhere.`},{key:"Shift+Space",behavior:'selectionMode="multiple" only: selects the contiguous run of visible nodes between the most recently *selected* node and the focused one, inclusive, in one update. The anchor is the last node selected, not the last node focused, so walking with plain arrows and then pressing Shift+Space extends from wherever the selection last landed \u2014 which is the point of the binding. With no anchor yet (or one that has since been collapsed away) it selects the focused node alone, exactly as Space would.'},{key:"Ctrl+ArrowUp/Ctrl+ArrowDown",behavior:"Identical to plain ArrowUp/ArrowDown: moves focus to the previous/next visible node without touching the selection. There is no separate handler, and none is needed \u2014 selection never follows focus in this tree, so the plain arrows already do what the modifier asks for. It is listed because a user arriving from APG's alternative multi-select model, where the plain arrows *do* move the selection, will reach for it; and because a Control-modified arrow must not start a typeahead search, which is the one thing that would otherwise differ."},{key:"Control+Shift+Home/Control+Shift+End",behavior:`selectionMode="multiple" only: moves focus to the first/last visible node and selects every node from the previously focused one through it. This is APG's binding. The bare Shift+Home/Shift+End form is kept as an alias for one release \u2014 it is what the component shipped with \u2014 but is not the documented one. Plain Home/End moves focus only.`},{key:"Ctrl+A",behavior:'selectionMode="multiple" only: selects every currently-visible node, or clears the selection when every one of them is already selected. Focus does not move. The toggle matters because the same chord is the only affordance a user has for undoing it. The scope is the *rendered* tree rather than the whole tree, which is as far as it can reach: a collapsed branch is unmounted, and the root holds no list of what is inside one.'},{key:"Enter",behavior:'Selects the focused node *and* fires the activate event. APG: "Activates a node, i.e., performs its default action."'},{key:"Space",behavior:'Selects the focused node and nothing else \u2014 never fires activate. APG: "Toggles the selection state of the focused node."'}],ariaAttributes:['aria-label or aria-labelledby on the root, supplied by the consumer \u2014 normative for role="tree" (see `namingRequirement`)','aria-multiselectable on the root when selectionMode is "multiple"',"each item's aria-level reflects its nesting depth","aria-setsize and aria-posinset (1-based) on every item, counting its siblings at the same level under the same parent \u2014 required rather than optional here because collapsed subtrees are unmounted, so assistive tech cannot infer a set it cannot see","author-supplied aria-level, aria-posinset and aria-setsize override the computed ones. This is contracted, not incidental: it is what the virtualization pattern depends on \u2014 a windowed tree renders a slice of the nodes and has to state the real positions itself \u2014 and it is exactly the case APG requires the attributes for. Every framework spreads consumer attributes after its own, and a Tree item given an explicit aria-posinset must render that value",`the computed pair is measured from the rendered DOM after mount, so server-rendered HTML carries neither. A non-hydrated tree \u2014 an Astro island, an RSC subtree \u2014 must supply them itself through the override above, which is the same instruction the virtualization pattern already gives. There is no equivalent of the roving tab stop's "every item is a tab stop until the effect runs" fallback here: a guessed position is a wrong position, and a wrong "3 of 7" is worse than none`,'aria-selected reflects selection unless selectionMode is "none" \u2014 or selectionPropagation is "descendants", where aria-checked carries the state instead and aria-selected is omitted entirely',"aria-expanded is set only on branch items \u2014 those with a nested TreeGroup, plus those declared as branches through the item's hasChildren, which is how a lazy branch announces itself before its children exist",'aria-busy="true" on an item whose loadChildren call is still pending','aria-checked (true/false/"mixed") on every item when selectionPropagation is "descendants" \u2014 never alongside aria-selected: APG allows either, and on a parent whose children are partly selected the two contradict each other ("false" and "mixed" at once)'],namingRequirement:`The root must carry an accessible name via aria-label or aria-labelledby, supplied by the consumer. This is normative rather than advisory \u2014 APG: "The element with role tree has either a visible label referenced by aria-labelledby or a value specified for aria-label." No prop is needed, because passthrough already carries both attributes in all five packages; and nothing inside the Tree can supply it, since role="tree" is name-from-author only and the node labels inside it name the tree no more than a list's items name the list. An unnamed tree announces as a bare "tree", which is indistinguishable from every other tree on the page.`,focusManagement:'Single tab stop with roving tabindex over currently-visible items only \u2014 collapsed subtrees are unmounted, not merely hidden, so they are naturally excluded (under disabledBehavior="selection" the disabled ones stay in). The stop is seeded from the first rendered id present in selectedIds, falling back to the first rendered id \u2014 APG: "If a node is selected before the tree receives focus, focus is set on the selected node", and for a multi-select tree "focus is set on the first selected node". It is re-seeded by the same rule whenever the item holding it is removed, so collapsing or unmounting the focused branch cannot leave the tree with no tabbable item. Only the innermost treeitem under the pointer acts on a click: items nest, so a click on a leaf also reaches every ancestor handler, which would otherwise select and collapse the whole branch above it.'},styling:{cssParts:["root","item","group","item-indicator"],dataAttributes:[...e,"data-state","data-disabled","data-loading","data-checked","data-text-value","data-skdx-tree-node-id","data-skdx-tree-parent-id"],cssCustomProperties:["--skdx-tree-node-depth"]},publicMethods:[{name:"invalidate",signature:"(nodeId?: string): void",description:"Clears the loaded marker on a node so the next expansion fetches again. With nodeId, clears that node and every child it has ever rendered; omitted, clears the whole tree. The consumer owns fetching and caching."}],frameworkExtensions:[{framework:"angular",name:"ControlValueAccessor",type:"ControlValueAccessor",description:"Registers the tree as a Reactive Forms control so [(ngModel)] and formControlName carry the selection. It adds one behaviour no other package has: setDisabledState() disables the whole tree, emitting aria-disabled and data-disabled on the root and refusing selection changes, select-many and select-all. Angular-only because Reactive Forms exists nowhere else \u2014 the other four reach the same place through the controlled `selectedIds` prop and its change event, which Tree already declares."}]};export{a as treeContract};
@@ -1 +1 @@
1
- import{changeDetail as e}from"./change-reason.js";import{ROOT_DATA_ATTRIBUTES as t}from"./styling-hooks.js";var o={name:"VerificationPage",purpose:"A one-time-code block composing Heading, Text, Alert, Form, Fieldset, PinInput and Button. It collects a code of a fixed length, reports it through a verify event once it is complete, and offers a resend control whose cooldown the consumer owns. Ships in the blocks package of each framework, not the core package.",props:[{name:"title",type:"string",defaultValue:"'Enter your verification code'",required:!1,description:"Heading text, rendered into a derived <h1> that names the section."},{name:"description",type:"string",required:!1,description:"Supporting copy rendered into a derived Text below the title, naming where the code was sent. No description region renders when omitted."},{name:"error",type:"string",required:!1,description:"Message rendered into a derived error Alert above the form. The block never clears it: set it from the verify handler and reset it when the next attempt starts."},{name:"pending",type:"boolean",defaultValue:"false",required:!1,description:"Marks the request in flight, making the submit button busy and unusable while the code boxes stay editable, so the user can still correct a digit."},{name:"codeLabel",type:"string",defaultValue:"'Verification code'",required:!1,description:"Legend of the code fieldset and accessible name of the derived pin input holding the code, so the group of boxes is announced as one control."},{name:"length",type:"number",defaultValue:"6",required:!1,description:"How many character boxes the code is entered into, and how long a complete code is before it may be submitted."},{name:"value",type:"string",required:!1,description:"Current code, for controlled usage; pair it with the value-change event. Uncontrolled, the pin input tracks the code itself."},{name:"defaultValue",type:"string",required:!1,description:"Initial code, for uncontrolled usage \u2014 seeds the boxes on first render and leaves the rest empty."},{name:"submitLabel",type:"string",defaultValue:"'Verify'",required:!1,description:"Text of the form submit button."},{name:"resendLabel",type:"string",defaultValue:"'Resend code'",required:!1,description:"Text of the resend button, which renders only alongside a resend event handler."},{name:"resendDisabled",type:"boolean",defaultValue:"false",required:!1,description:"Makes the resend button unusable, for a consumer running its own cooldown between requests."},{name:"resendHint",type:"string",required:!1,description:"Note rendered beside the resend button, such as how long the cooldown has left. The block runs no timer of its own."},{name:"footer",type:"unknown",required:!1,frameworks:["react","svelte","angular"],exception:"render-prop",frameworksReason:"Vue and Web Components take this as a named slot rather than a prop \u2014 Vue because a renderable region is a slot in Vue's own idiom, Web Components because a custom element attribute carries a string and cannot carry a node. Both slot spellings are declared under `slots`.",description:"Navigation rendered below the resend region, such as a link back to the sign-in page. No footer region renders when omitted. A prop in React, Svelte, and Angular; Vue and Web Components use the `footer` slot."}],events:[{name:"verify",detail:"{ code: string }",description:"Fires when the form is submitted with a code of exactly the declared length, carrying that code. The native submit is prevented, so the consumer performs the check and sets error on failure."},{name:"resend",detail:"void",description:"Fires when the resend button is activated. Handling it is what renders the resend region at all; the cooldown between requests belongs to the consumer."},{name:"value-change",detail:e("string"),description:"Fires with the new code whenever any box changes, controlled or not, so a consumer can mirror the code or clear a stale error."}],slots:[{name:"default",description:"Extra fields rendered inside the form, above the submit button."},{name:"footer",description:"Navigation below the resend region, such as a link back to the sign-in page."}],states:["default"],controlledUncontrolled:"The code is controllable through value with the value-change event, or seeded with defaultValue and tracked by the pin input; error and pending are always controlled by the consumer.",formBehavior:"The form is a core Form root whose pin input submits the joined code as one required entry with a length pattern, so a short code is refused by constraint validation and emits nothing.",validationBehavior:"The code is required and only satisfies its constraint once every box is filled; the block adds no validators and never times the resend cooldown.",accessibility:{role:"region (native <section> named by its heading through aria-labelledby)",keyboard:[{key:"ArrowLeft/ArrowRight",behavior:"Moves between the code boxes, which the pin input wires itself."},{key:"Enter",behavior:"Submits the form while a code box has focus (native form submission)."}],ariaAttributes:["aria-labelledby on the <section> points at the derived heading","the pin input group carries aria-label from codeLabel, so the boxes are announced as one named control","the derived error Alert is a live region, so a new error is announced without moving focus"],focusManagement:"Focus follows the code boxes as the pin input advances them; a refused submit reports through constraint validation rather than a jump of the block's own."},styling:{cssParts:["verification-page","panel","title","description","error","form","field","code","submit","resend","resend-hint","footer"],dataAttributes:[...t,{name:"data-skdx-verification-page-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.'},{name:"data-skdx-verification-page-content",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{o as verificationPageContract};
1
+ import{changeDetail as e}from"./change-reason.js";import{ROOT_DATA_ATTRIBUTES as t}from"./styling-hooks.js";var o={name:"VerificationPage",purpose:"A one-time-code block composing Heading, Text, Alert, Form, Fieldset, PinInput and Button. It collects a code of a fixed length, reports it through a verify event once it is complete, and offers a resend control whose cooldown the consumer owns. Ships in the blocks package of each framework, not the core package.",props:[{name:"title",type:"string",defaultValue:"'Enter your verification code'",required:!1,description:"Heading text, rendered into a derived <h1> that names the section."},{name:"description",type:"string",required:!1,description:"Supporting copy rendered into a derived Text below the title, naming where the code was sent. No description region renders when omitted."},{name:"error",type:"string",required:!1,description:"Message rendered into a derived error Alert above the form. The block never clears it: set it from the verify handler and reset it when the next attempt starts."},{name:"pending",type:"boolean",defaultValue:"false",required:!1,description:"Marks the request in flight, making the submit button busy and unusable while the code boxes stay editable, so the user can still correct a digit."},{name:"codeLabel",type:"string",defaultValue:"'Verification code'",required:!1,description:"Legend of the code fieldset and accessible name of the derived pin input holding the code, so the group of boxes is announced as one control."},{name:"length",type:"number",defaultValue:"6",required:!1,description:"How many character boxes the code is entered into, and how long a complete code is before it may be submitted."},{name:"value",type:"string",required:!1,description:"Current code, for controlled usage; pair it with the value-change event. Uncontrolled, the pin input tracks the code itself."},{name:"defaultValue",type:"string",required:!1,description:"Initial code, for uncontrolled usage \u2014 seeds the boxes on first render and leaves the rest empty."},{name:"submitLabel",type:"string",defaultValue:"'Verify'",required:!1,description:"Text of the form submit button."},{name:"resendLabel",type:"string",defaultValue:"'Resend code'",required:!1,description:"Text of the resend button, which renders only alongside a resend event handler."},{name:"resendDisabled",type:"boolean",defaultValue:"false",required:!1,description:"Makes the resend button unusable, for a consumer running its own cooldown between requests."},{name:"resendHint",type:"string",required:!1,description:"Note rendered beside the resend button, such as how long the cooldown has left. The block runs no timer of its own."},{name:"footer",type:"unknown",required:!1,frameworks:["react","svelte","angular"],exception:"render-prop",frameworksReason:"Vue and Web Components take this as a named slot rather than a prop \u2014 Vue because a renderable region is a slot in Vue's own idiom, Web Components because a custom element attribute carries a string and cannot carry a node. Both slot spellings are declared under `slots`.",description:"Navigation rendered below the resend region, such as a link back to the sign-in page. No footer region renders when omitted. A prop in React, Svelte, and Angular; Vue and Web Components use the `footer` slot."}],events:[{name:"verify",detail:"{ code: string }",description:"Fires when the form is submitted with a code of exactly the declared length, carrying that code. The native submit is prevented, so the consumer performs the check and sets error on failure."},{name:"resend",detail:"void",description:"Fires when the resend button is activated. Handling it is what renders the resend region at all; the cooldown between requests belongs to the consumer."},{name:"value-change",detail:e("string"),description:"Fires with the new code whenever any box changes, controlled or not, so a consumer can mirror the code or clear a stale error."}],slots:[{name:"default",description:"Extra fields rendered inside the form, above the submit button."},{name:"footer",description:"Navigation below the resend region, such as a link back to the sign-in page."}],states:["default"],controlledUncontrolled:"The code is controllable through value with the value-change event, or seeded with defaultValue and tracked by the pin input; error and pending are always controlled by the consumer.",formBehavior:"The form is a core Form root whose pin input submits the joined code as one required entry with a length pattern, so a short code is refused by constraint validation and emits nothing.",validationBehavior:"The code is required and only satisfies its constraint once every box is filled. A refused submit is reported on the visible box group \u2014 aria-invalid on the group and the browser's own wording in the field's error part \u2014 since the constraint lives on a hidden mirror input no message can be anchored to; the next edit clears it. The block adds no validators and never times the resend cooldown.",accessibility:{role:"region (native <section> named by its heading through aria-labelledby)",keyboard:[{key:"ArrowLeft/ArrowRight",behavior:"Moves between the code boxes, which the pin input wires itself."},{key:"Enter",behavior:"Submits the form while a code box has focus (native form submission)."}],ariaAttributes:["aria-labelledby on the <section> points at the derived heading","the pin input group carries aria-label from codeLabel, so the boxes are announced as one named control","the derived error Alert is a live region, so a new error is announced without moving focus"],focusManagement:"Focus follows the code boxes as the pin input advances them; a refused submit reports through constraint validation rather than a jump of the block's own."},styling:{cssParts:["verification-page","panel","title","description","error","form","field","code","submit","resend","resend-hint","footer"],dataAttributes:[...t,{name:"data-skdx-verification-page-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.'},{name:"data-skdx-verification-page-content",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{o as verificationPageContract};
@@ -1 +1 @@
1
- import{autoFocusEvent as u}from"./dismissal.js";import{TABBABLE_SELECTOR as i}from"./tabbable.js";var s={Dialog:"the Cancel button when present, then the first tabbable element, then the panel itself",Drawer:"the first tabbable element, then the panel itself",Popover:"the first tabbable element, then the panel itself"},f={Dialog:"[data-skdx-dialog-cancel]"},d={Dialog:"the dialog panel",Drawer:"the drawer panel",Popover:"the popover panel"};function a(e,n,t,o){if(!n)return null;let r;try{r=e.querySelector(n)}catch{return console.warn(`[skdx] ${o}: ${t}="${n}" is not a valid CSS selector. Falling back to the default focus target.`),null}return r||console.warn(t==="initialFocus"?`[skdx] ${o}: initialFocus="${n}" matched no element inside ${d[o]}. Falling back to ${s[o]}.`:`[skdx] ${o}: finalFocus="${n}" matched no element in the document. Falling back to the element that was focused before it opened.`),r}function c(e,n){let t=f[n];return t?e.querySelector(t):null}function T(e,n,t){return a(e,n,"initialFocus",t)??c(e,t)??e.querySelector(i)??e}function m(e,n,t){let o=a(e,n,"initialFocus",t)??c(e,t);if(o){o.focus({preventScroll:!0});return}if(e.contains(document.activeElement))return;let l=e.querySelector(i)??e;l===e&&e.tabIndex<0&&(e.tabIndex=-1),l.focus?.({preventScroll:!0})}function F(e,n){let t=a(document,e,"finalFocus",n);return t?(t.focus({preventScroll:!0}),!0):!1}function h(e,n,t,o,r){let l=document.activeElement;return u("open-auto-focus",o)?(m(e,n,t),!0):(document.activeElement===l&&r?.isConnected&&e.contains(l)&&r.focus({preventScroll:!0}),!1)}function E(e,n,t,o){return u("close-auto-focus",o)?F(e,n)?!0:!t?.isConnected||t===document.body||t===document.documentElement?!1:(t.focus({preventScroll:!0}),!0):!1}export{F as applyFinalFocus,m as applyInitialFocus,h as applyOpenAutoFocus,T as initialFocusTarget,a as resolveFocusSelector,E as returnFocus};
1
+ import{autoFocusEvent as i}from"./dismissal.js";import{TABBABLE_SELECTOR as a}from"./tabbable.js";var d={Dialog:"the Cancel button when present, then the first tabbable element, then the panel itself",Drawer:"the first tabbable element, then the panel itself",Popover:"the first tabbable element, then the panel itself"},f={Dialog:"[data-skdx-dialog-cancel]"},m={Dialog:"the dialog panel",Drawer:"the drawer panel",Popover:"the popover panel"};function u(e,n,t,o){if(!n)return null;let l;try{l=e.querySelector(n)}catch{return console.warn(`[skdx] ${o}: ${t}="${n}" is not a valid CSS selector. Falling back to the default focus target.`),null}return l||console.warn(t==="initialFocus"?`[skdx] ${o}: initialFocus="${n}" matched no element inside ${m[o]}. Falling back to ${d[o]}.`:`[skdx] ${o}: finalFocus="${n}" matched no element in the document. Falling back to the element that was focused before it opened.`),l}function c(e,n){let t=f[n];return t?e.querySelector(t):null}function E(e,n,t){return u(e,n,"initialFocus",t)??c(e,t)??e.querySelector(a)??e}function F(e,n,t){let o=u(e,n,"initialFocus",t)??c(e,t);if(o){o.focus({preventScroll:!0});return}if(e.contains(document.activeElement))return;let r=e.querySelector(a)??e;r===e&&e.tabIndex<0&&(e.tabIndex=-1),r.focus?.({preventScroll:!0})}function s(e){e.focus({preventScroll:!0}),setTimeout(()=>{let n=document.activeElement;e.isConnected&&(n===null||n===document.body)&&e.focus({preventScroll:!0})})}function T(e,n){let t=u(document,e,"finalFocus",n);return t?(s(t),!0):!1}function h(e,n,t,o,l){let r=document.activeElement;return i("open-auto-focus",o)?(F(e,n,t),!0):(document.activeElement===r&&l?.isConnected&&e.contains(r)&&l.focus({preventScroll:!0}),!1)}function v(e,n,t,o){return i("close-auto-focus",o)?T(e,n)?!0:!t?.isConnected||t===document.body||t===document.documentElement?!1:(s(t),!0):!1}export{T as applyFinalFocus,F as applyInitialFocus,h as applyOpenAutoFocus,E as initialFocusTarget,u as resolveFocusSelector,v as returnFocus};
@@ -1 +1 @@
1
- var l='a[href], button, input, select, textarea, [tabindex]:not([tabindex="-1"])',i="radio, checkbox, switch, menuitemcheckbox, menuitemradio",u='button, [role="radio"], [role="checkbox"], [role="switch"]';function s(t){let e=t instanceof HTMLButtonElement||i.split(", ").includes(t.getAttribute("role")??"")?t:t.querySelector(u);return!(e instanceof HTMLElement)||e instanceof HTMLInputElement||e.disabled||e.getAttribute("aria-disabled")==="true"?null:e}function a(t,n){let e=`#${CSS.escape(n)}`;for(let o=t.parentElement;o;o=o.parentElement){let r=o.querySelector(e);if(r)return r}return t.getRootNode().getElementById(n)}function f(t,n){if(n.defaultPrevented)return;let e=t.getAttribute("for");if(!e)return;let o=n.target instanceof Element?n.target:null;if(o&&o!==t&&o.closest(l)!==t.closest(l))return;let r=a(t,e),c=r&&s(r);c&&(n.preventDefault(),c.focus(),c.click())}export{f as activateLabelledButton};
1
+ var c='a[href], button, input, select, textarea, [tabindex]:not([tabindex="-1"])',i="radio, checkbox, switch, menuitemcheckbox, menuitemradio",u='button, [role="radio"], [role="checkbox"], [role="switch"]';function s(t){let e=t instanceof HTMLButtonElement||i.split(", ").includes(t.getAttribute("role")??"")?t:t.querySelector(u);return!(e instanceof HTMLElement)||e instanceof HTMLInputElement||e.disabled||e.getAttribute("aria-disabled")==="true"?null:e}function a(t,n){let e=`[id="${n.replace(/["\\]/g,"\\$&")}"]`;for(let o=t.parentElement;o;o=o.parentElement){let r=o.querySelector(e);if(r)return r}return t.getRootNode().getElementById(n)}function f(t,n){if(n.defaultPrevented)return;let e=t.getAttribute("for");if(!e)return;let o=n.target instanceof Element?n.target:null;if(o&&o!==t&&o.closest(c)!==t.closest(c))return;let r=a(t,e),l=r&&s(r);l&&(n.preventDefault(),l.focus(),l.click())}export{f as activateLabelledButton};
@@ -82,6 +82,7 @@
82
82
  /* Commonly holds two things (a button, a "see more" link); no text node survives
83
83
  between JSX siblings, so without this gap they'd render flush together. */
84
84
  display: inline-flex;
85
+ flex-wrap: wrap;
85
86
  align-items: center;
86
87
  gap: var(--skdx-space-3);
87
88
  }
@@ -73,6 +73,16 @@
73
73
  z-index: var(--skdx-z-index-base);
74
74
  }
75
75
 
76
+ /* APG renders the rotation control before the track, so it claims the root's first wrap line and
77
+ the track no longer starts at the block start; without this the arrows sit on top of it and the
78
+ start-edge arrow makes Pause unclickable. */
79
+ :where(.skdx-carousel):has(> .skdx-carousel__play-pause)
80
+ :where(.skdx-carousel__previous, .skdx-carousel__next) {
81
+ inset-block-start: calc(
82
+ var(--skdx-control-height-md) + var(--skdx-space-2) + var(--skdx-space-16) / 2
83
+ );
84
+ }
85
+
76
86
  :where(.skdx-carousel__previous) {
77
87
  inset-inline-start: var(--skdx-space-2);
78
88
  }
@@ -96,6 +96,26 @@
96
96
  min-inline-size: 0;
97
97
  }
98
98
 
99
+ /* Below the sm breakpoint (640px) a 16rem column leaves the content unreadable
100
+ — 86px of <main> on a 400px screen — so an open nav overlays it instead of
101
+ splitting the row. `navOpen` still decides whether the nav is there at all. */
102
+ @media (max-width: 40rem) {
103
+ :where(.skdx-dashboard-layout__body) {
104
+ position: relative;
105
+ grid-template-columns: minmax(0, 1fr);
106
+ }
107
+
108
+ :where(.skdx-dashboard-layout__nav) {
109
+ position: absolute;
110
+ z-index: 1;
111
+ inset-block: 0;
112
+ inset-inline-start: 0;
113
+ max-inline-size: 100%;
114
+ background-color: var(--skdx-color-surface-raised);
115
+ box-shadow: var(--skdx-shadow-overlay);
116
+ }
117
+ }
118
+
99
119
  @media (forced-colors: active) {
100
120
  :where(.skdx-dashboard-layout__app-bar),
101
121
  :where(.skdx-dashboard-layout__nav),
@@ -107,4 +107,10 @@
107
107
  flex-basis: 100%;
108
108
  margin-inline-start: calc(-1 * var(--skdx-tree-item-indent));
109
109
  }
110
+
111
+ /* forceMount keeps a collapsed branch's group in the DOM; painting it would contradict the
112
+ owning item's aria-expanded="false". Override this rule to animate the exit. */
113
+ :where(.skdx-tree__group)[data-state='collapsed'] {
114
+ display: none;
115
+ }
110
116
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skdx/docs-kit",
3
- "version": "0.38.0",
3
+ "version": "0.39.0",
4
4
  "description": "Build-time toolkit for the SKDX documentation site: the shared contracts, the framework table, generated manifests and the source files the docs measure. Not a public API — no semver guarantees, may change in any release.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/SkandaDX/ui.skdx/tree/main/internal/docs-kit#readme",
@@ -36,19 +36,19 @@
36
36
  "access": "public"
37
37
  },
38
38
  "devDependencies": {
39
- "@skdx/angular-blocks": "0.38.0",
40
- "@skdx/react-blocks": "0.38.0",
41
- "@skdx/react": "0.38.0",
42
- "@skdx/svelte": "0.38.0",
43
- "@skdx/skins": "0.38.0",
44
- "@skdx/shared": "0.38.0",
45
- "@skdx/angular": "0.38.0",
46
- "@skdx/vue-blocks": "0.38.0",
47
- "@skdx/web-components": "0.38.0",
48
- "@skdx/tokens": "0.38.0",
49
- "@skdx/vue": "0.38.0",
50
- "@skdx/svelte-blocks": "0.38.0",
51
- "@skdx/web-components-blocks": "0.38.0"
39
+ "@skdx/angular": "0.39.0",
40
+ "@skdx/react-blocks": "0.39.0",
41
+ "@skdx/angular-blocks": "0.39.0",
42
+ "@skdx/react": "0.39.0",
43
+ "@skdx/shared": "0.39.0",
44
+ "@skdx/skins": "0.39.0",
45
+ "@skdx/vue-blocks": "0.39.0",
46
+ "@skdx/vue": "0.39.0",
47
+ "@skdx/svelte-blocks": "0.39.0",
48
+ "@skdx/web-components": "0.39.0",
49
+ "@skdx/svelte": "0.39.0",
50
+ "@skdx/web-components-blocks": "0.39.0",
51
+ "@skdx/tokens": "0.39.0"
52
52
  },
53
53
  "nx": {
54
54
  "targets": {