@astryxdesign/core 0.7.1-canary.b359d5c → 0.7.1-canary.bffff80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +191 -32
- package/dist/Chat/ChatMessage.d.ts +11 -0
- package/dist/Chat/ChatMessage.d.ts.map +1 -1
- package/dist/Chat/ChatMessage.js +18 -9
- package/dist/CommandPalette/CommandPalette.d.ts +9 -5
- package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPalette.js +38 -20
- package/dist/CommandPalette/CommandPaletteEmpty.d.ts +4 -3
- package/dist/CommandPalette/CommandPaletteEmpty.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteEmpty.js +4 -3
- package/dist/CommandPalette/CommandPaletteInput.d.ts +5 -4
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +35 -18
- package/dist/CommandPalette/CommandPaletteItem.d.ts +11 -6
- package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteItem.js +8 -9
- package/dist/CommandPalette/CommandPaletteList.d.ts +4 -2
- package/dist/CommandPalette/CommandPaletteList.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteList.js +10 -5
- package/dist/ComplexSelector/ComplexSelector.d.ts +8 -5
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +12 -3
- package/dist/ContextMenu/ContextMenu.d.ts +3 -3
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +62 -48
- package/dist/DateInput/DateInput.js +1 -1
- package/dist/DateInput/MonthScroller.d.ts.map +1 -1
- package/dist/DateInput/MonthScroller.js +12 -4
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +12 -2
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +140 -63
- package/dist/FormLayout/FormLayout.d.ts.map +1 -1
- package/dist/FormLayout/FormLayout.js +1 -0
- package/dist/Grid/Grid.d.ts +13 -4
- package/dist/Grid/Grid.d.ts.map +1 -1
- package/dist/Grid/Grid.js +7 -1
- package/dist/HoverCard/useHoverCard.d.ts +1 -1
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +67 -49
- package/dist/Layer/useLayerDismissal.d.ts +9 -9
- package/dist/Layer/useLayerDismissal.js +9 -9
- package/dist/MobileNav/MobileNav.d.ts +7 -0
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +45 -2
- package/dist/MultiSelector/MultiSelector.d.ts +7 -4
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +24 -3
- package/dist/NumberInput/NumberInput.js +1 -1
- package/dist/Resizable/ResizeHandle.d.ts +1 -1
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +83 -15
- package/dist/Resizable/useResizable.d.ts +23 -0
- package/dist/Resizable/useResizable.d.ts.map +1 -1
- package/dist/Resizable/useResizable.js +78 -0
- package/dist/Selector/Selector.d.ts +6 -3
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +20 -6
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +10 -2
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +16 -4
- package/dist/Toolbar/Toolbar.d.ts +2 -1
- package/dist/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/Toolbar/Toolbar.js +3 -1
- package/dist/astryx.css +1 -0
- package/dist/hooks/useScrollOverflow.d.ts.map +1 -1
- package/dist/hooks/useScrollOverflow.js +55 -12
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +2 -0
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +2 -0
- package/dist/reset.until-found.a11y.browser.spec.d.ts +2 -0
- package/dist/reset.until-found.a11y.browser.spec.d.ts.map +1 -0
- package/dist/reset.until-found.a11y.browser.spec.js +71 -0
- package/dist/utils/plainDate.d.ts +12 -0
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +39 -5
- package/locales/en.json +9 -1
- package/locales/pseudo.json +6 -0
- package/package.json +3 -3
- package/src/AlertDialog/AlertDialog.test.tsx +3 -14
- package/src/AlertDialog/__tests__/AlertDialog.a11y.chromium.spec.ts +115 -0
- package/src/AlertDialog/__tests__/AlertDialog.a11y.renders.tsx +52 -0
- package/src/AlertDialog/__tests__/AlertDialog.a11y.states.ts +68 -0
- package/src/AlertDialog/__tests__/AlertDialog.a11y.test.tsx +144 -0
- package/src/Calendar/Calendar.spec.md +19 -27
- package/src/Calendar/Calendar.test.tsx +534 -20
- package/src/Calendar/Calendar.tsx +258 -41
- package/src/Calendar/__tests__/Calendar.a11y.chromium.spec.ts +126 -0
- package/src/Calendar/__tests__/Calendar.a11y.known-failures.ts +12 -0
- package/src/Calendar/__tests__/Calendar.a11y.renders.tsx +57 -0
- package/src/Calendar/__tests__/Calendar.a11y.states.ts +139 -0
- package/src/Calendar/__tests__/Calendar.a11y.test.tsx +137 -0
- package/src/Carousel/Carousel.test.tsx +54 -71
- package/src/Carousel/__tests__/Carousel.a11y.chromium.spec.ts +192 -0
- package/src/Carousel/__tests__/Carousel.a11y.known-failures.ts +27 -0
- package/src/Carousel/__tests__/Carousel.a11y.renders.tsx +39 -0
- package/src/Carousel/__tests__/Carousel.a11y.states.ts +139 -0
- package/src/Carousel/__tests__/Carousel.a11y.test.tsx +127 -0
- package/src/Chat/ChatComposerInput.spec.md +95 -56
- package/src/Chat/ChatMessage.doc.mjs +31 -16
- package/src/Chat/ChatMessage.spec.md +265 -0
- package/src/Chat/ChatMessage.test.tsx +246 -0
- package/src/Chat/ChatMessage.tsx +30 -7
- package/src/CommandPalette/CommandPalette.doc.mjs +126 -23
- package/src/CommandPalette/CommandPalette.spec.md +100 -49
- package/src/CommandPalette/CommandPalette.test.tsx +241 -2
- package/src/CommandPalette/CommandPalette.tsx +47 -21
- package/src/CommandPalette/CommandPaletteEmpty.tsx +4 -3
- package/src/CommandPalette/CommandPaletteInput.doc.mjs +61 -4
- package/src/CommandPalette/CommandPaletteInput.spec.md +215 -0
- package/src/CommandPalette/CommandPaletteInput.test.tsx +375 -51
- package/src/CommandPalette/CommandPaletteInput.tsx +74 -21
- package/src/CommandPalette/CommandPaletteItem.doc.mjs +73 -22
- package/src/CommandPalette/CommandPaletteItem.spec.md +223 -0
- package/src/CommandPalette/CommandPaletteItem.test.tsx +200 -5
- package/src/CommandPalette/CommandPaletteItem.tsx +16 -11
- package/src/CommandPalette/CommandPaletteList.doc.mjs +3 -3
- package/src/CommandPalette/CommandPaletteList.spec.md +41 -36
- package/src/CommandPalette/CommandPaletteList.test.tsx +80 -0
- package/src/CommandPalette/CommandPaletteList.tsx +10 -5
- package/src/CommandPalette/__tests__/CommandPalette.a11y.chromium.spec.ts +599 -0
- package/src/ComplexSelector/ComplexSelector.doc.mjs +10 -8
- package/src/ComplexSelector/ComplexSelector.test.tsx +64 -16
- package/src/ComplexSelector/ComplexSelector.tsx +37 -8
- package/src/ContextMenu/ContextMenu.doc.mjs +6 -0
- package/src/ContextMenu/ContextMenu.spec.md +51 -53
- package/src/ContextMenu/ContextMenu.test.tsx +132 -1
- package/src/ContextMenu/ContextMenu.tsx +78 -48
- package/src/DateInput/DateInput.doc.mjs +52 -0
- package/src/DateInput/DateInput.spec.md +20 -20
- package/src/DateInput/DateInput.test.tsx +38 -0
- package/src/DateInput/DateInput.tsx +1 -1
- package/src/DateInput/DateInputTouch.test.tsx +22 -0
- package/src/DateInput/MonthScroller.tsx +12 -4
- package/src/DateRangeInput/DateRangeInput.test.tsx +49 -0
- package/src/DateRangeInput/DateRangeInput.tsx +20 -2
- package/src/DropdownMenu/DropdownMenu.doc.mjs +2 -2
- package/src/DropdownMenu/DropdownMenu.spec.md +73 -47
- package/src/DropdownMenu/DropdownMenu.test.tsx +239 -23
- package/src/DropdownMenu/DropdownMenu.tsx +189 -79
- package/src/DropdownMenu/__tests__/MenuHeaderFooter.a11y.chromium.spec.ts +40 -5
- package/src/FormLayout/FormLayout.tsx +1 -0
- package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +1 -1
- package/src/Grid/Grid.doc.mjs +14 -11
- package/src/Grid/Grid.tsx +13 -4
- package/src/HoverCard/HoverCard.test.tsx +46 -1
- package/src/HoverCard/useHoverCard.tsx +68 -48
- package/src/Layer/useLayerDismissal.ts +9 -9
- package/src/MobileNav/MobileNav.tsx +50 -2
- package/src/MobileNav/MobileNavFocus.test.tsx +553 -0
- package/src/MultiSelector/MultiSelector.doc.mjs +8 -8
- package/src/MultiSelector/MultiSelector.test.tsx +188 -21
- package/src/MultiSelector/MultiSelector.tsx +50 -11
- package/src/NumberInput/NumberInput.test.tsx +24 -0
- package/src/NumberInput/NumberInput.tsx +1 -1
- package/src/Resizable/Resizable.spec.md +32 -23
- package/src/Resizable/ResizeHandle.test.tsx +565 -2
- package/src/Resizable/ResizeHandle.tsx +126 -21
- package/src/Resizable/__tests__/ResizeHandle.a11y.chromium.spec.ts +114 -0
- package/src/Resizable/__tests__/ResizeHandle.a11y.known-failures.ts +39 -0
- package/src/Resizable/__tests__/ResizeHandle.a11y.renders.tsx +80 -0
- package/src/Resizable/__tests__/ResizeHandle.a11y.states.ts +197 -0
- package/src/Resizable/__tests__/ResizeHandle.a11y.test.tsx +123 -0
- package/src/Resizable/useResizable.ts +95 -0
- package/src/Selector/Selector.doc.mjs +7 -6
- package/src/Selector/Selector.spec.md +14 -11
- package/src/Selector/Selector.test.tsx +124 -10
- package/src/Selector/Selector.tsx +51 -14
- package/src/Selector/__tests__/Combobox.a11y.known-failures.ts +27 -0
- package/src/Selector/__tests__/Combobox.a11y.states.ts +145 -0
- package/src/Selector/__tests__/Selector.combobox.a11y.chromium.spec.ts +108 -0
- package/src/Selector/__tests__/Selector.combobox.a11y.test.tsx +126 -0
- package/src/SideNav/SideNav.test.tsx +35 -0
- package/src/SideNav/SideNav.tsx +16 -2
- package/src/Tokenizer/Tokenizer.test.tsx +87 -0
- package/src/Tokenizer/Tokenizer.tsx +31 -3
- package/src/Toolbar/Toolbar.test.tsx +131 -4
- package/src/Toolbar/Toolbar.tsx +3 -1
- package/src/TreeList/__tests__/TreeList.a11y.chromium.spec.ts +125 -0
- package/src/TreeList/__tests__/TreeList.a11y.renders.tsx +44 -0
- package/src/TreeList/__tests__/TreeList.a11y.states.ts +164 -0
- package/src/TreeList/__tests__/TreeList.a11y.test.tsx +138 -0
- package/src/hooks/useScrollOverflow.test.tsx +222 -0
- package/src/hooks/useScrollOverflow.ts +55 -13
- package/src/i18n/generated-locales/en.generated.ts +2 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +2 -0
- package/src/reset.css +4 -2
- package/src/reset.until-found.a11y.browser.spec.ts +75 -0
- package/src/reset.until-found.test.ts +32 -0
- package/src/stylexValidation.source-build.test.mjs +42 -0
- package/src/utils/plainDate.test.ts +111 -0
- package/src/utils/plainDate.ts +48 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Calendar.d.ts","sourceRoot":"","sources":["../../src/Calendar/Calendar.tsx"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"Calendar.d.ts","sourceRoot":"","sources":["../../src/Calendar/Calendar.tsx"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAsD5C,YAAY,EACV,aAAa,EACb,SAAS,EACT,aAAa,EACb,SAAS,GACV,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EACV,aAAa,EACb,SAAS,EACT,aAAa,EACb,SAAS,EACV,MAAM,oBAAoB,CAAC;AAS5B,+CAA+C;AAC/C,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,UAAU,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;CAC3C;AAID,UAAU,iBAAkB,SAAQ,IAAI,CACtC,SAAS,CAAC,cAAc,CAAC,EACzB,UAAU,GAAG,cAAc,CAC5B;IACC,kDAAkD;IAClD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC,qDAAqD;IACrD,SAAS,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IACtC,+CAA+C;IAC/C,cAAc,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IAEvB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,aAAa,CAAC;IAEpB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,aAAa,CAAC;IAEpB;;;OAGG;IACH,eAAe,CAAC,EAAE,aAAa,CAAC,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC;IAEzD;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC;IAE1B,yDAAyD;IACzD,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,aAAa,KAAK,IAAI,CAAC;IAEvD;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,GAAG,aAAa,CAAC;CAC1C;AAID,UAAU,mBAAoB,SAAQ,iBAAiB;IACrD,qBAAqB;IACrB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAEhB,mFAAmF;IACnF,KAAK,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAE7B,0CAA0C;IAC1C,YAAY,CAAC,EAAE,aAAa,CAAC;IAE7B,qCAAqC;IACrC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,IAAI,KAAK,IAAI,CAAC;CAC9D;AAED,UAAU,kBAAmB,SAAQ,iBAAiB;IACpD,qBAAqB;IACrB,IAAI,EAAE,OAAO,CAAC;IAEd,8DAA8D;IAC9D,KAAK,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAEzB,0CAA0C;IAC1C,YAAY,CAAC,EAAE,SAAS,CAAC;IAEzB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CACvC;AAED,MAAM,MAAM,aAAa,GAAG,mBAAmB,GAAG,kBAAkB,CAAC;AAqBrE;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,EAAC,GAAG,EAAE,GAAG,KAAK,EAAC,EAAE,aAAa,+BAghBtD;yBAhhBe,QAAQ"}
|
|
@@ -24,7 +24,7 @@ import { Icon } from "../Icon/index.js";
|
|
|
24
24
|
import { useAnnounce, useGridFocus } from "../hooks/index.js";
|
|
25
25
|
import { useCalendarDays, useCalendarConstraints } from "./hooks/index.js";
|
|
26
26
|
import { calendarStyles, monthGridStyles, dayCellStyles, dayCellTheme } from "./styles.js";
|
|
27
|
-
import { plainDateFromISO, plainDateToISO, plainDateToDate, plainDateToday, plainDateSetFirstOfMonth, plainDateAddMonths, plainDateAddDays, plainDateIsBefore, plainDateIsEqual, plainDateGetWeekNumber, plainDateFormat, DATE_FORMAT_WITH_WEEKDAY, DATE_FORMAT_MONTH_YEAR } from "../utils/plainDate.js";
|
|
27
|
+
import { plainDateFromISO, plainDateToISO, plainDateToDate, plainDateToday, plainDateSetFirstOfMonth, plainDateAddMonths, plainDateAddDays, plainDateDiffDays, getDaysInMonth, plainDateIsAfter, plainDateIsBefore, plainDateIsEqual, plainDateGetWeekNumber, plainDateFormat, plainDateWeekFieldName, DATE_FORMAT_WITH_WEEKDAY, DATE_FORMAT_MONTH_YEAR } from "../utils/plainDate.js";
|
|
28
28
|
import { mergeProps, composeEventHandlers, rtlStyles } from "../utils/index.js";
|
|
29
29
|
import { focusOutlineProps } from "../utils/focusOutline.stylex.js";
|
|
30
30
|
import { interactionOverlayStyles } from "../utils/interactionOverlay.stylex.js";
|
|
@@ -39,6 +39,9 @@ import { computeDayCellState, computeRangeRounding, computePreviewRounding, comp
|
|
|
39
39
|
import { normalizeDayOfWeek } from "../utils/dateTypes.js";
|
|
40
40
|
import { themeProps } from "../utils/themeProps.js";
|
|
41
41
|
import { useLocale, useTranslator } from "../i18n/index.js";
|
|
42
|
+
import { VisuallyHidden } from "../VisuallyHidden/index.js";
|
|
43
|
+
|
|
44
|
+
/** How a month navigation moves the focused day: by its row offset in days, or by whole months. */
|
|
42
45
|
|
|
43
46
|
/** Imperative handle for Calendar handleRef */
|
|
44
47
|
|
|
@@ -46,6 +49,22 @@ import { useLocale, useTranslator } from "../i18n/index.js";
|
|
|
46
49
|
|
|
47
50
|
// ─── Mode-specific Props (discriminated union) ────────────────
|
|
48
51
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
52
|
+
/** The first day of `month`'s month that `isDisabled` allows, or null. */
|
|
53
|
+
function firstEnabledDayOfMonth(month, isDisabled) {
|
|
54
|
+
const days = getDaysInMonth(month.year, month.month);
|
|
55
|
+
for (let day = 1; day <= days; day += 1) {
|
|
56
|
+
const pd = {
|
|
57
|
+
year: month.year,
|
|
58
|
+
month: month.month,
|
|
59
|
+
day
|
|
60
|
+
};
|
|
61
|
+
if (!isDisabled(pd)) {
|
|
62
|
+
return pd;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
49
68
|
// =============================================================================
|
|
50
69
|
// Main Component
|
|
51
70
|
// =============================================================================
|
|
@@ -157,6 +176,30 @@ export function Calendar({
|
|
|
157
176
|
});
|
|
158
177
|
}, [baseMonth, numberOfMonths]);
|
|
159
178
|
|
|
179
|
+
// A pending target is claimed by the grid that shows its month, or dropped
|
|
180
|
+
// by the next focus move that is not onto the target day: when a controlled
|
|
181
|
+
// owner refuses the month change, no grid shows the target's month, and a
|
|
182
|
+
// stale target would later steal focus (and Enter) when that month does
|
|
183
|
+
// appear. Only a grid showing the target's month receives it; any other
|
|
184
|
+
// focus change (an arrow to another day, the month header's buttons, or out
|
|
185
|
+
// of the calendar) drops it (see onBlur below).
|
|
186
|
+
const claimablePendingFocus = useMemo(() => {
|
|
187
|
+
if (pendingFocus == null) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
const target = plainDateFromISO(pendingFocus);
|
|
191
|
+
return visibleMonths.some(pd => pd.year === target.year && pd.month === target.month) ? pendingFocus : null;
|
|
192
|
+
}, [pendingFocus, visibleMonths]);
|
|
193
|
+
const handleRootBlur = useCallback(e => {
|
|
194
|
+
// Only the move onto the target day itself keeps the target: an arrow
|
|
195
|
+
// to any other day, a move to the header buttons, or focus leaving the
|
|
196
|
+
// calendar all drop it.
|
|
197
|
+
const movesToTarget = e.relatedTarget instanceof Element && e.relatedTarget.getAttribute('data-date') === pendingFocus;
|
|
198
|
+
if (pendingFocus != null && !movesToTarget) {
|
|
199
|
+
setPendingFocus(null);
|
|
200
|
+
}
|
|
201
|
+
}, [pendingFocus]);
|
|
202
|
+
|
|
160
203
|
// Format month header
|
|
161
204
|
const monthYearLabel = useMemo(() => {
|
|
162
205
|
if (numberOfMonths === 1) {
|
|
@@ -185,6 +228,8 @@ export function Calendar({
|
|
|
185
228
|
}, [monthYearLabel, announce]);
|
|
186
229
|
|
|
187
230
|
// Determine if prev/next navigation is possible based on min/max
|
|
231
|
+
const minDateForKeys = useMemo(() => min ? plainDateFromISO(min) : null, [min]);
|
|
232
|
+
const maxDateForKeys = useMemo(() => max ? plainDateFromISO(max) : null, [max]);
|
|
188
233
|
const canNavigatePrevious = useMemo(() => {
|
|
189
234
|
if (!min) {
|
|
190
235
|
return true;
|
|
@@ -204,23 +249,109 @@ export function Calendar({
|
|
|
204
249
|
}, [max, baseMonth, numberOfMonths]);
|
|
205
250
|
|
|
206
251
|
// Navigation handlers
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
252
|
+
// The same constraint view the grids use, so a key can resolve its target
|
|
253
|
+
// before the month changes.
|
|
254
|
+
const rootRangeAnchor = useMemo(() => mode === 'range' && rangeSelectionStart ? plainDateFromISO(rangeSelectionStart) : null, [mode, rangeSelectionStart]);
|
|
255
|
+
const {
|
|
256
|
+
isDateDisabled: isRootDateDisabled
|
|
257
|
+
} = useCalendarConstraints({
|
|
258
|
+
min,
|
|
259
|
+
max,
|
|
260
|
+
dateConstraints,
|
|
261
|
+
maxRangeSpan,
|
|
262
|
+
minRangeSpan,
|
|
263
|
+
rangeAnchor: rootRangeAnchor
|
|
264
|
+
});
|
|
265
|
+
const navigateMonth = useCallback((delta, focusedDate, offset, unit = 'days') => {
|
|
266
|
+
const monthIndex = pd => pd.year * 12 + pd.month;
|
|
267
|
+
const firstShown = monthIndex(baseMonth);
|
|
268
|
+
const lastShown = firstShown + numberOfMonths - 1;
|
|
269
|
+
|
|
270
|
+
// The Previous/Next buttons move the month with no focused day.
|
|
271
|
+
if (!focusedDate) {
|
|
272
|
+
if (delta < 0 && !canNavigatePrevious || delta > 0 && !canNavigateNext) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const newPd = plainDateAddMonths(baseMonth, delta);
|
|
276
|
+
if (onFocusDateChange) {
|
|
277
|
+
onFocusDateChange(plainDateToISO(newPd));
|
|
278
|
+
} else {
|
|
279
|
+
setInternalFocusDate(newPd);
|
|
280
|
+
}
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
210
283
|
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
284
|
+
// A key resolves its TARGET day first and decides by that, so a
|
|
285
|
+
// boundary blocks only out-of-range targets, never the month movement
|
|
286
|
+
// itself (ArrowUp from February 3 reaches January 27 at a January
|
|
287
|
+
// minimum), and a direction with no enabled day left changes nothing
|
|
288
|
+
// rather than dropping focus.
|
|
289
|
+
const currentPd = plainDateFromISO(focusedDate);
|
|
290
|
+
const inRange = pd => !(minDateForKeys && plainDateIsBefore(pd, minDateForKeys)) && !(maxDateForKeys && plainDateIsAfter(pd, maxDateForKeys));
|
|
291
|
+
let targetPd;
|
|
292
|
+
if (unit === 'months') {
|
|
293
|
+
// A page targets the same day of the month a month away (clamped to
|
|
294
|
+
// that month's length). Past `min`/`max` nothing moves; a month with
|
|
295
|
+
// no enabled day is no destination either, the same as an arrow with
|
|
296
|
+
// no enabled day in its direction; a target disabled by a constraint
|
|
297
|
+
// lands on that month's first enabled day instead.
|
|
298
|
+
const candidate = plainDateAddMonths(currentPd, delta);
|
|
299
|
+
if (!inRange(candidate)) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (isRootDateDisabled(candidate)) {
|
|
303
|
+
const firstEnabled = firstEnabledDayOfMonth(candidate, isRootDateDisabled);
|
|
304
|
+
if (firstEnabled == null) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
targetPd = firstEnabled;
|
|
308
|
+
} else {
|
|
309
|
+
targetPd = candidate;
|
|
310
|
+
}
|
|
311
|
+
} else {
|
|
312
|
+
// Walk the arrow's direction to the first enabled day, up to a year
|
|
313
|
+
// away; past `min`/`max` or with every day disabled, nothing moves.
|
|
314
|
+
const step = delta * (offset ?? 7);
|
|
315
|
+
let candidate = plainDateAddDays(currentPd, step);
|
|
316
|
+
let found = null;
|
|
317
|
+
while (Math.abs(plainDateDiffDays(currentPd, candidate)) <= 366) {
|
|
318
|
+
if (!inRange(candidate)) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (!isRootDateDisabled(candidate)) {
|
|
322
|
+
found = candidate;
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
candidate = plainDateAddDays(candidate, step);
|
|
326
|
+
}
|
|
327
|
+
if (found == null) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
targetPd = found;
|
|
217
331
|
}
|
|
332
|
+
|
|
333
|
+
// Focus moves to the target; the months change only when the target's
|
|
334
|
+
// month is not already shown. The view shifts the least it can in the
|
|
335
|
+
// key's direction, so the target month becomes the first (backwards)
|
|
336
|
+
// or last (forwards) month shown.
|
|
337
|
+
const resolved = targetPd;
|
|
338
|
+
const targetMonth = monthIndex(resolved);
|
|
339
|
+
setPendingFocus(plainDateToISO(resolved));
|
|
340
|
+
if (targetMonth >= firstShown && targetMonth <= lastShown) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const targetFirst = {
|
|
344
|
+
year: resolved.year,
|
|
345
|
+
month: resolved.month,
|
|
346
|
+
day: 1
|
|
347
|
+
};
|
|
348
|
+
const newPd = targetMonth < firstShown ? targetFirst : plainDateAddMonths(targetFirst, -(numberOfMonths - 1));
|
|
218
349
|
if (onFocusDateChange) {
|
|
219
|
-
onFocusDateChange(
|
|
350
|
+
onFocusDateChange(plainDateToISO(newPd));
|
|
220
351
|
} else {
|
|
221
352
|
setInternalFocusDate(newPd);
|
|
222
353
|
}
|
|
223
|
-
}, [baseMonth, onFocusDateChange]);
|
|
354
|
+
}, [baseMonth, canNavigateNext, canNavigatePrevious, isRootDateDisabled, maxDateForKeys, minDateForKeys, numberOfMonths, onFocusDateChange]);
|
|
224
355
|
|
|
225
356
|
// Escape key handler to cancel range selection
|
|
226
357
|
const handleCalendarKeyDown = useCallback(e => {
|
|
@@ -294,6 +425,7 @@ export function Calendar({
|
|
|
294
425
|
return /*#__PURE__*/_jsxs("div", {
|
|
295
426
|
ref: ref,
|
|
296
427
|
...rest,
|
|
428
|
+
onBlur: composeEventHandlers(rest.onBlur, handleRootBlur),
|
|
297
429
|
...mergeProps(themeProps('calendar', {
|
|
298
430
|
mode
|
|
299
431
|
}), stylex.props(calendarStyles.calendar, xstyle), className, style),
|
|
@@ -365,9 +497,9 @@ export function Calendar({
|
|
|
365
497
|
onDayClick: handleDayClick,
|
|
366
498
|
onDayHover: date => setHoveredDate(date ? plainDateToISO(date) : null),
|
|
367
499
|
today: today,
|
|
368
|
-
onNavigatePrevious: (focusedDate, offset) => navigateMonth(-1, focusedDate, offset),
|
|
369
|
-
onNavigateNext: (focusedDate, offset) => navigateMonth(1, focusedDate, offset),
|
|
370
|
-
pendingFocus:
|
|
500
|
+
onNavigatePrevious: (focusedDate, offset, unit) => navigateMonth(-1, focusedDate, offset, unit),
|
|
501
|
+
onNavigateNext: (focusedDate, offset, unit) => navigateMonth(1, focusedDate, offset, unit),
|
|
502
|
+
pendingFocus: claimablePendingFocus,
|
|
371
503
|
onPendingFocusHandled: () => setPendingFocus(null)
|
|
372
504
|
}, `${month.year}-${month.month}`))
|
|
373
505
|
})]
|
|
@@ -501,13 +633,13 @@ function MonthGrid({
|
|
|
501
633
|
const handlePageUp = useCallback(() => {
|
|
502
634
|
const focusedDate = getFocusedDate();
|
|
503
635
|
if (focusedDate) {
|
|
504
|
-
onNavigatePrevious?.(focusedDate,
|
|
636
|
+
onNavigatePrevious?.(focusedDate, 1, 'months');
|
|
505
637
|
}
|
|
506
638
|
}, [getFocusedDate, onNavigatePrevious]);
|
|
507
639
|
const handlePageDown = useCallback(() => {
|
|
508
640
|
const focusedDate = getFocusedDate();
|
|
509
641
|
if (focusedDate) {
|
|
510
|
-
onNavigateNext?.(focusedDate,
|
|
642
|
+
onNavigateNext?.(focusedDate, 1, 'months');
|
|
511
643
|
}
|
|
512
644
|
}, [getFocusedDate, onNavigateNext]);
|
|
513
645
|
|
|
@@ -527,7 +659,12 @@ function MonthGrid({
|
|
|
527
659
|
} = useGridFocus({
|
|
528
660
|
columns: 7,
|
|
529
661
|
cellSelector: '[role="gridcell"]',
|
|
530
|
-
|
|
662
|
+
// A borrowed adjacent-month day is exposed aria-disabled (its click does
|
|
663
|
+
// nothing, but it can still take pointer focus) and may also be natively
|
|
664
|
+
// disabled by bounds or a constraint; the keyboard skips it either way
|
|
665
|
+
// and crosses into that month through the grid edge instead, so focus
|
|
666
|
+
// lands on the real day in the month now shown (FR5).
|
|
667
|
+
isCellFocusable: cell => cell.querySelector('button:not([disabled]):not([aria-disabled="true"])') !== null,
|
|
531
668
|
getFocusTarget: cell => cell.querySelector('button'),
|
|
532
669
|
hasRovingTabIndex: true,
|
|
533
670
|
onNavigateBefore: handleNavigatePrevious,
|
|
@@ -536,27 +673,38 @@ function MonthGrid({
|
|
|
536
673
|
onPageDown: handlePageDown
|
|
537
674
|
});
|
|
538
675
|
|
|
539
|
-
// Handle pending focus after month navigation
|
|
676
|
+
// Handle pending focus after month navigation. Every mounted month grid
|
|
677
|
+
// sees the same pending date; only the grid showing that date's month acts,
|
|
678
|
+
// so a neighbouring grid's fallback never overrides the right target. The
|
|
679
|
+
// target is the real day in this month, never an outside-day copy; when it
|
|
680
|
+
// is disabled, the month's first enabled day takes focus instead.
|
|
540
681
|
useEffect(() => {
|
|
541
682
|
if (!pendingFocus || !gridRef.current) {
|
|
542
683
|
return;
|
|
543
684
|
}
|
|
544
|
-
const buttons = gridRef.current.querySelectorAll('button:not([disabled])');
|
|
545
685
|
const targetPd = plainDateFromISO(pendingFocus);
|
|
686
|
+
if (targetPd.year !== year || targetPd.month !== month.month) {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
546
689
|
const targetIso = plainDateToISO(targetPd);
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
690
|
+
const buttons = Array.from(gridRef.current.querySelectorAll('button[data-date]:not([disabled]):not([aria-disabled="true"])'));
|
|
691
|
+
|
|
692
|
+
// The real target day, else the month's first enabled day, else — in a
|
|
693
|
+
// month with no enabled day — the first day button that can still take
|
|
694
|
+
// focus (a borrowed adjacent-month day), so focus stays in the grid
|
|
695
|
+
// rather than dropping to the page.
|
|
696
|
+
const targetButton = buttons.find(button => button.getAttribute('data-date') === targetIso) ?? buttons[0] ?? gridRef.current.querySelector('button[data-date]:not([disabled])') ?? null;
|
|
697
|
+
if (targetButton != null) {
|
|
698
|
+
// The target becomes this grid's one Tab stop before it takes focus,
|
|
699
|
+
// the way the hook's own arrow moves do (FR2): a programmatic focus
|
|
700
|
+
// alone would leave the stop on the day the grid mounted with.
|
|
701
|
+
for (const button of gridRef.current.querySelectorAll('button[data-date]')) {
|
|
702
|
+
button.tabIndex = button === targetButton ? 0 : -1;
|
|
552
703
|
}
|
|
704
|
+
targetButton.focus();
|
|
553
705
|
}
|
|
554
|
-
if (!targetButton && buttons.length > 0) {
|
|
555
|
-
targetButton = buttons[0];
|
|
556
|
-
}
|
|
557
|
-
targetButton?.focus();
|
|
558
706
|
onPendingFocusHandled?.();
|
|
559
|
-
}, [pendingFocus, gridRef, onPendingFocusHandled]);
|
|
707
|
+
}, [pendingFocus, gridRef, month.month, onPendingFocusHandled, year]);
|
|
560
708
|
|
|
561
709
|
// Parse selection
|
|
562
710
|
let selectedDate = null;
|
|
@@ -613,8 +761,19 @@ function MonthGrid({
|
|
|
613
761
|
children: [/*#__PURE__*/_jsxs("div", {
|
|
614
762
|
role: "row",
|
|
615
763
|
...stylex.props(monthGridStyles.weekRow),
|
|
616
|
-
children: [hasWeekNumbers &&
|
|
617
|
-
|
|
764
|
+
children: [hasWeekNumbers &&
|
|
765
|
+
/*#__PURE__*/
|
|
766
|
+
// The corner above the week-number column is a header cell like
|
|
767
|
+
// the weekday names beside it (APG grid: every cell in a row is a
|
|
768
|
+
// gridcell, columnheader or rowheader). Its name is the locale's
|
|
769
|
+
// own word for the week field, as visually hidden text so the
|
|
770
|
+
// column stays blank and the header is not empty to axe.
|
|
771
|
+
_jsx("div", {
|
|
772
|
+
role: "columnheader",
|
|
773
|
+
...stylex.props(monthGridStyles.dayName, monthGridStyles.weekNumberHeader),
|
|
774
|
+
children: /*#__PURE__*/_jsx(VisuallyHidden, {
|
|
775
|
+
children: plainDateWeekFieldName(locale)
|
|
776
|
+
})
|
|
618
777
|
}), dayNameHeaders.map(({
|
|
619
778
|
dayOfWeek,
|
|
620
779
|
name
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* SYNC: When modified, update these files to stay in sync:
|
|
13
13
|
* - /packages/core/src/Chat/index.ts (exports)
|
|
14
14
|
* - /apps/storybook/stories/Chat.stories.tsx
|
|
15
|
+
* - /apps/storybook/stories/ChatMessage.stories.tsx
|
|
15
16
|
* - /packages/cli/assets/templates/blocks/components/ChatMessage/ (block examples)
|
|
16
17
|
*/
|
|
17
18
|
import { type ReactNode } from 'react';
|
|
@@ -28,9 +29,17 @@ export interface ChatMessageProps extends BaseProps<HTMLElement> {
|
|
|
28
29
|
* should span the full message column.
|
|
29
30
|
*/
|
|
30
31
|
children: ReactNode;
|
|
32
|
+
/**
|
|
33
|
+
* Avatar content rendered beside a non-system message.
|
|
34
|
+
* `null`, booleans, and empty strings create no slot; any other content that
|
|
35
|
+
* renders nothing collapses its slot.
|
|
36
|
+
*/
|
|
31
37
|
avatar?: ReactNode;
|
|
32
38
|
/**
|
|
33
39
|
* Sender name rendered above the message body.
|
|
40
|
+
* `null`, booleans, and empty strings create no slot, and the message is
|
|
41
|
+
* labelled by the translated sender fallback; any other content that renders
|
|
42
|
+
* nothing collapses its slot and falls back to the same label.
|
|
34
43
|
* Use when the first child is raw content (not a bubble).
|
|
35
44
|
* If the first child is a ChatMessageBubble, put the name on the
|
|
36
45
|
* bubble's `name` prop instead — it aligns with the bubble's padding.
|
|
@@ -38,6 +47,8 @@ export interface ChatMessageProps extends BaseProps<HTMLElement> {
|
|
|
38
47
|
name?: ReactNode;
|
|
39
48
|
/**
|
|
40
49
|
* Metadata rendered below the message body.
|
|
50
|
+
* `null`, booleans, and empty strings create no slot; any other content that
|
|
51
|
+
* renders nothing collapses its slot.
|
|
41
52
|
* Use when the last child is raw content (not a bubble).
|
|
42
53
|
* If the last child is a ChatMessageBubble, put metadata on the
|
|
43
54
|
* bubble's `metadata` prop instead — it aligns with the bubble's padding.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatMessage.d.ts","sourceRoot":"","sources":["../../src/Chat/ChatMessage.tsx"],"names":[],"mappings":"AAIA
|
|
1
|
+
{"version":3,"file":"ChatMessage.d.ts","sourceRoot":"","sources":["../../src/Chat/ChatMessage.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAC,KAAK,SAAS,EAAiB,MAAM,OAAO,CAAC;AAQrD,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,WAAW,EACjB,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAI5C,MAAM,WAAW,gBAAiB,SAAQ,SAAS,CAAC,WAAW,CAAC;IAC9D,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC7B,MAAM,EAAE,iBAAiB,CAAC;IAC1B;;;;;;OAMG;IACH,QAAQ,EAAE,SAAS,CAAC;IACpB;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AA4FD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,QAAQ,EACR,MAAM,EACN,IAAI,EACJ,QAAQ,EACR,OAAO,EAAE,WAAW,EACpB,MAAM,EACN,SAAS,EACT,KAAK,EAAE,SAAS,EAChB,aAAa,EAAE,MAAM,EACrB,GAAG,EACH,GAAG,IAAI,EACR,EAAE,gBAAgB,+BA+FlB;yBA5Ge,WAAW"}
|
package/dist/Chat/ChatMessage.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* SYNC: When modified, update these files to stay in sync:
|
|
17
17
|
* - /packages/core/src/Chat/index.ts (exports)
|
|
18
18
|
* - /apps/storybook/stories/Chat.stories.tsx
|
|
19
|
+
* - /apps/storybook/stories/ChatMessage.stories.tsx
|
|
19
20
|
* - /packages/cli/assets/templates/blocks/components/ChatMessage/ (block examples)
|
|
20
21
|
*/
|
|
21
22
|
import { useMemo, useId } from 'react';
|
|
@@ -23,7 +24,7 @@ import * as stylex from '@stylexjs/stylex';
|
|
|
23
24
|
import "../theme/tokens.stylex.js";
|
|
24
25
|
import { colorVars, spacingVars, typeScaleVars, fontWeightVars } from "../theme/tokens.stylex.js";
|
|
25
26
|
import { ChatMessageContext, useChatListContext } from "./ChatContext.js";
|
|
26
|
-
import { mergeProps } from "../utils/index.js";
|
|
27
|
+
import { isRenderable, mergeProps } from "../utils/index.js";
|
|
27
28
|
import { themeProps } from "../utils/themeProps.js";
|
|
28
29
|
import { useTranslator } from "../i18n/index.js";
|
|
29
30
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
@@ -155,18 +156,23 @@ export function ChatMessage({
|
|
|
155
156
|
const columnAlignment = sender === 'system' ? styles.contentColumnSystem : sender === 'user' ? styles.contentColumnUser : styles.contentColumnAssistant;
|
|
156
157
|
const childrenAlignment = sender === 'system' ? styles.childrenSystem : sender === 'user' ? styles.childrenUser : styles.childrenAssistant;
|
|
157
158
|
const isSystem = sender === 'system';
|
|
158
|
-
const hasAvatar =
|
|
159
|
-
const hasName =
|
|
159
|
+
const hasAvatar = !isSystem && isRenderable(avatar);
|
|
160
|
+
const hasName = !isSystem && isRenderable(name);
|
|
161
|
+
const hasMetadata = !isSystem && isRenderable(metadata);
|
|
160
162
|
const nameId = useId();
|
|
161
163
|
return /*#__PURE__*/_jsx(ChatMessageContext, {
|
|
162
164
|
value: contextValue,
|
|
163
165
|
children: /*#__PURE__*/_jsxs("article", {
|
|
164
166
|
...rest,
|
|
165
167
|
ref: ref,
|
|
166
|
-
"data-testid": testId
|
|
167
|
-
|
|
168
|
+
"data-testid": testId
|
|
169
|
+
// The visible name labels the article. When it renders no text, the
|
|
170
|
+
// label reference resolves to nothing and the accessible name falls
|
|
171
|
+
// back to aria-label (accessible name computation 1.2, step 2B).
|
|
172
|
+
,
|
|
173
|
+
"aria-label": t('@astryx.chatMessage.messageFrom', {
|
|
168
174
|
sender
|
|
169
|
-
})
|
|
175
|
+
}),
|
|
170
176
|
"aria-labelledby": hasName ? nameId : undefined,
|
|
171
177
|
...mergeProps(themeProps('chat-message', {
|
|
172
178
|
sender,
|
|
@@ -174,7 +180,7 @@ export function ChatMessage({
|
|
|
174
180
|
}), stylex.props(styles.root, rootAlignment, hasAvatar && rootGap, xstyle), className, styleProp),
|
|
175
181
|
children: [hasAvatar && /*#__PURE__*/_jsx("div", {
|
|
176
182
|
...{
|
|
177
|
-
className: "x2lah0s x5du68j"
|
|
183
|
+
className: "xjl7jj x2lah0s x5du68j"
|
|
178
184
|
},
|
|
179
185
|
children: avatar
|
|
180
186
|
}), /*#__PURE__*/_jsxs("div", {
|
|
@@ -182,13 +188,16 @@ export function ChatMessage({
|
|
|
182
188
|
children: [hasName && /*#__PURE__*/_jsx("div", {
|
|
183
189
|
id: nameId,
|
|
184
190
|
...{
|
|
185
|
-
className: "x141an7d x2mo6ok xv1l7n4 x1ltkj2j x14cgwvg"
|
|
191
|
+
className: "xjl7jj x141an7d x2mo6ok xv1l7n4 x1ltkj2j x14cgwvg"
|
|
186
192
|
},
|
|
187
193
|
children: name
|
|
188
194
|
}), /*#__PURE__*/_jsx("div", {
|
|
189
195
|
...stylex.props(styles.childrenWrap, childrenAlignment, childrenGap),
|
|
190
196
|
children: children
|
|
191
|
-
}),
|
|
197
|
+
}), hasMetadata && /*#__PURE__*/_jsx("div", {
|
|
198
|
+
...{
|
|
199
|
+
className: "xjl7jj"
|
|
200
|
+
},
|
|
192
201
|
children: metadata
|
|
193
202
|
})]
|
|
194
203
|
})]
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file CommandPalette.tsx
|
|
3
|
-
* @input Uses React, Dialog, Layout, CommandPaletteContext, SearchSource,
|
|
3
|
+
* @input Uses React, Dialog, Layout, CommandPaletteContext, SearchSource,
|
|
4
|
+
* useCombobox, useAnnounce, useAnnounceRenderedText
|
|
4
5
|
* @output Exports CommandPalette root component and props
|
|
5
6
|
* @position Core root component; dialog shell with searchSource-driven items
|
|
6
7
|
*
|
|
@@ -42,7 +43,9 @@ export interface CommandPaletteProps<T extends SearchableItem = SearchableItem>
|
|
|
42
43
|
footer?: ReactNode;
|
|
43
44
|
/**
|
|
44
45
|
* Per-item render function. Receives the item and whether it is currently selected.
|
|
45
|
-
*
|
|
46
|
+
* Return non-interactive presentation content; buttons and links cannot be
|
|
47
|
+
* nested inside the rendered option. Auto-grouping by `auxiliaryData.group`
|
|
48
|
+
* is preserved.
|
|
46
49
|
* When omitted, renders each item's `label` text.
|
|
47
50
|
*/
|
|
48
51
|
renderItem?: (item: T, isSelected: boolean) => ReactNode;
|
|
@@ -82,9 +85,10 @@ export interface CommandPaletteProps<T extends SearchableItem = SearchableItem>
|
|
|
82
85
|
* Uses `searchSource` for all search logic — same interface as Typeahead.
|
|
83
86
|
* For static lists, use `createStaticSource` from `@astryxdesign/core/Typeahead`.
|
|
84
87
|
*
|
|
85
|
-
* Keyboard navigation is handled by `useCombobox` from Selector,
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
+
* Keyboard navigation is handled by `useCombobox` from Selector for arrows,
|
|
89
|
+
* PageUp/PageDown, and Enter. Home/End remain native caret keys. Modal Escape
|
|
90
|
+
* dismissal delegates to Dialog's shared layer stack so nested surfaces handle
|
|
91
|
+
* the first press.
|
|
88
92
|
*
|
|
89
93
|
* Input and footer are rendered by default — only pass them to replace the defaults.
|
|
90
94
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommandPalette.d.ts","sourceRoot":"","sources":["../../src/CommandPalette/CommandPalette.tsx"],"names":[],"mappings":"AAGA
|
|
1
|
+
{"version":3,"file":"CommandPalette.d.ts","sourceRoot":"","sources":["../../src/CommandPalette/CommandPalette.tsx"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AAEH,OAAO,EASL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAAC,YAAY,EAAE,cAAc,EAAC,MAAM,cAAc,CAAC;AAU/D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAK5C,MAAM,WAAW,mBAAmB,CAClC,CAAC,SAAS,cAAc,GAAG,cAAc,CACzC,SAAQ,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,EAAE,UAAU,CAAC;IACtD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACnC,2CAA2C;IAC3C,MAAM,EAAE,OAAO,CAAC;IAEhB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,0DAA0D;IAC1D,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAExC;;;;OAIG;IACH,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IAE9B;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAElB;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IAEnB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,UAAU,EAAE,OAAO,KAAK,SAAS,CAAC;IAEzD;;;OAGG;IACH,eAAe,CAAC,EAAE,SAAS,CAAC;IAE5B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,SAAS,CAAC;IAE/B,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,8CAA8C;IAC9C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAExC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAExB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC7B;AAiID;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc,EAAE,EACxE,GAAG,EACH,MAAM,EACN,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,KAAK,EACL,MAAM,EACN,UAAU,EACV,eAAe,EAAE,wBAAwB,EACzC,kBAAkB,EAAE,2BAA2B,EAC/C,KAAK,EAAE,eAAe,EACtB,aAAa,EACb,KAAK,EAAE,cAAc,EACrB,KAAW,EACX,SAAe,EACf,GAAG,IAAI,EACR,EAAE,mBAAmB,CAAC,CAAC,CAAC,+BA2YxB;yBA5Ze,cAAc"}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file CommandPalette.tsx
|
|
7
|
-
* @input Uses React, Dialog, Layout, CommandPaletteContext, SearchSource,
|
|
7
|
+
* @input Uses React, Dialog, Layout, CommandPaletteContext, SearchSource,
|
|
8
|
+
* useCombobox, useAnnounce, useAnnounceRenderedText
|
|
8
9
|
* @output Exports CommandPalette root component and props
|
|
9
10
|
* @position Core root component; dialog shell with searchSource-driven items
|
|
10
11
|
*
|
|
@@ -24,6 +25,7 @@ import { CommandPaletteInput } from "./CommandPaletteInput.js";
|
|
|
24
25
|
import { CommandPaletteFooter } from "./CommandPaletteFooter.js";
|
|
25
26
|
import { CommandPaletteEmpty } from "./CommandPaletteEmpty.js";
|
|
26
27
|
import { useAnnounce } from "../hooks/useAnnounce.js";
|
|
28
|
+
import { useAnnounceRenderedText } from "../hooks/useAnnounceRenderedText.js";
|
|
27
29
|
import { useTranslator } from "../i18n/index.js";
|
|
28
30
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
29
31
|
function getGroup(item) {
|
|
@@ -145,9 +147,10 @@ function ItemRenderer({
|
|
|
145
147
|
* Uses `searchSource` for all search logic — same interface as Typeahead.
|
|
146
148
|
* For static lists, use `createStaticSource` from `@astryxdesign/core/Typeahead`.
|
|
147
149
|
*
|
|
148
|
-
* Keyboard navigation is handled by `useCombobox` from Selector,
|
|
149
|
-
*
|
|
150
|
-
*
|
|
150
|
+
* Keyboard navigation is handled by `useCombobox` from Selector for arrows,
|
|
151
|
+
* PageUp/PageDown, and Enter. Home/End remain native caret keys. Modal Escape
|
|
152
|
+
* dismissal delegates to Dialog's shared layer stack so nested surfaces handle
|
|
153
|
+
* the first press.
|
|
151
154
|
*
|
|
152
155
|
* Input and footer are rendered by default — only pass them to replace the defaults.
|
|
153
156
|
*
|
|
@@ -200,6 +203,7 @@ export function CommandPalette({
|
|
|
200
203
|
const [optimisticResults, setOptimisticResults] = useOptimistic(searchResults);
|
|
201
204
|
const isBusy = isPending;
|
|
202
205
|
const searchVersionRef = useRef(0);
|
|
206
|
+
const emptyStateRef = useRef(null);
|
|
203
207
|
|
|
204
208
|
// Announce search status to screen readers through the shared polite live
|
|
205
209
|
// region (comboboxes-7 announce path, mirroring Selector / BaseTypeahead).
|
|
@@ -267,6 +271,10 @@ export function CommandPalette({
|
|
|
267
271
|
const runSearch = useCallback(query => {
|
|
268
272
|
searchSource.cancel?.();
|
|
269
273
|
const version = ++searchVersionRef.current;
|
|
274
|
+
// A highlight belongs to the query that created it. Clear it before any
|
|
275
|
+
// optimistic narrowing so aria-activedescendant never shifts to a
|
|
276
|
+
// different command at the same index while the next result set loads.
|
|
277
|
+
combobox.setHighlightedIndex(-1);
|
|
270
278
|
startTransition(async () => {
|
|
271
279
|
const isBootstrap = query === '';
|
|
272
280
|
|
|
@@ -293,18 +301,12 @@ export function CommandPalette({
|
|
|
293
301
|
|
|
294
302
|
// Announce the outcome from the search commit (not a reactive
|
|
295
303
|
// effect), matching Selector / BaseTypeahead: exactly one
|
|
296
|
-
// announcement per committed query
|
|
297
|
-
//
|
|
298
|
-
//
|
|
299
|
-
// palette announces nothing; clearing the query only clears any
|
|
300
|
-
// lingering status text.
|
|
304
|
+
// announcement per committed non-empty query. Empty results are
|
|
305
|
+
// announced from their rendered content below so a caller-supplied
|
|
306
|
+
// ReactNode reaches assistive technology as written.
|
|
301
307
|
if (isBootstrap) {
|
|
302
308
|
announce('');
|
|
303
|
-
} else if (items.length
|
|
304
|
-
announce(t('@astryx.commandPalette.noResultsFor', {
|
|
305
|
-
query
|
|
306
|
-
}));
|
|
307
|
-
} else {
|
|
309
|
+
} else if (items.length > 0) {
|
|
308
310
|
announce(t('@astryx.commandPalette.resultCount', {
|
|
309
311
|
count: items.length
|
|
310
312
|
}));
|
|
@@ -346,13 +348,18 @@ export function CommandPalette({
|
|
|
346
348
|
}
|
|
347
349
|
}, [isOpen]);
|
|
348
350
|
|
|
349
|
-
// Wrap combobox's onKeyDown to
|
|
350
|
-
//
|
|
351
|
-
//
|
|
351
|
+
// Wrap combobox's onKeyDown to preserve inline-preview Escape handling and
|
|
352
|
+
// select + close on Enter. Modal Escape stays unclaimed here so Dialog's
|
|
353
|
+
// shared dismissal stack can route it to the topmost surface.
|
|
352
354
|
const handleKeyDown = useCallback(e => {
|
|
353
355
|
if (e.key === 'Escape') {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
+
// Modal CommandPalette delegates Escape to Dialog's shared dismissal
|
|
357
|
+
// stack so a nested layer handles the first press. Inline previews do
|
|
358
|
+
// not join that stack, so preserve their local close behavior.
|
|
359
|
+
if (isInline) {
|
|
360
|
+
e.preventDefault();
|
|
361
|
+
handleClose();
|
|
362
|
+
}
|
|
356
363
|
return;
|
|
357
364
|
}
|
|
358
365
|
if (e.key === 'Enter') {
|
|
@@ -366,12 +373,17 @@ export function CommandPalette({
|
|
|
366
373
|
}
|
|
367
374
|
return;
|
|
368
375
|
}
|
|
376
|
+
// Home and End remain native caret-movement keys for this editable
|
|
377
|
+
// combobox. PageUp/PageDown provide first/last result navigation.
|
|
378
|
+
if (e.key === 'Home' || e.key === 'End') {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
369
381
|
// Space should type in the input, not trigger selection
|
|
370
382
|
if (e.key === ' ') {
|
|
371
383
|
return;
|
|
372
384
|
}
|
|
373
385
|
combobox.onKeyDown(e);
|
|
374
|
-
}, [combobox, handleClose, selectableItems, selectItem]);
|
|
386
|
+
}, [combobox, handleClose, isInline, selectableItems, selectItem]);
|
|
375
387
|
|
|
376
388
|
// Hover highlight is owned here by a single delegated handler on the list
|
|
377
389
|
// container: it routes to useCombobox's hover-aware path, which moves the
|
|
@@ -427,13 +439,19 @@ export function CommandPalette({
|
|
|
427
439
|
// the empty state is never unmounted and re-added mid-search (which flashed).
|
|
428
440
|
const showEmptyBootstrap = search === '' && optimisticResults.length === 0;
|
|
429
441
|
const showEmptySearch = search !== '' && optimisticResults.length === 0;
|
|
442
|
+
// Announce only once the search settles: while it is pending, optimistic
|
|
443
|
+
// narrowing can empty the list before the source answers, which would
|
|
444
|
+
// speak "No results" early or wrongly.
|
|
445
|
+
useAnnounceRenderedText(emptyStateRef, showEmptySearch && !isPending, search);
|
|
430
446
|
let listContent;
|
|
431
447
|
if (showEmptyBootstrap) {
|
|
432
448
|
listContent = /*#__PURE__*/_jsx(CommandPaletteEmpty, {
|
|
449
|
+
ref: emptyStateRef,
|
|
433
450
|
children: emptyBootstrapText
|
|
434
451
|
});
|
|
435
452
|
} else if (showEmptySearch) {
|
|
436
453
|
listContent = /*#__PURE__*/_jsx(CommandPaletteEmpty, {
|
|
454
|
+
ref: emptyStateRef,
|
|
437
455
|
children: emptySearchText
|
|
438
456
|
});
|
|
439
457
|
} else {
|
|
@@ -12,13 +12,14 @@ export interface CommandPaletteEmptyProps extends BaseProps<HTMLDivElement> {
|
|
|
12
12
|
* - `emptyBootstrapText`: no search term and bootstrap() returns nothing
|
|
13
13
|
* - `emptySearchText`: a search query returned no results
|
|
14
14
|
*
|
|
15
|
-
* Can also be composed
|
|
15
|
+
* Can also be composed as a standalone empty result surface. Render actionable
|
|
16
|
+
* empty content outside listbox and option semantics.
|
|
16
17
|
*
|
|
17
18
|
* @example
|
|
18
19
|
* ```
|
|
19
20
|
* <CommandPalette
|
|
20
|
-
* emptyBootstrapText=
|
|
21
|
-
* emptySearchText=
|
|
21
|
+
* emptyBootstrapText="Start typing to search"
|
|
22
|
+
* emptySearchText="No results found"
|
|
22
23
|
* searchSource={source}
|
|
23
24
|
* />
|
|
24
25
|
* ```
|