@astryxdesign/core 0.7.2-canary.55d8dff → 0.7.2-canary.577a62e
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/README.md +4 -4
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +8 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.js +4 -1
- package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
- package/dist/Chat/ChatLayoutScrollButton.js +1 -0
- package/dist/CheckboxList/CheckboxList.d.ts +1 -1
- package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxList.js +6 -2
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +2 -103
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +1 -1
- package/dist/DropdownMenu/DropdownMenuGroup.d.ts +6 -4
- package/dist/DropdownMenu/DropdownMenuGroup.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuGroup.js +4 -3
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +1 -1
- package/dist/DropdownMenu/menuRegionStops.d.ts +38 -0
- package/dist/DropdownMenu/menuRegionStops.d.ts.map +1 -0
- package/dist/DropdownMenu/menuRegionStops.js +131 -0
- package/dist/FormLayout/FormLayout.d.ts +5 -4
- package/dist/FormLayout/FormLayout.d.ts.map +1 -1
- package/dist/FormLayout/FormLayout.js +1 -1
- package/dist/FormLayout/FormLayoutContext.d.ts +3 -3
- package/dist/FormLayout/FormLayoutContext.js +3 -3
- package/dist/Grid/Grid.d.ts +8 -7
- package/dist/Grid/Grid.d.ts.map +1 -1
- package/dist/Grid/Grid.js +5 -4
- package/dist/Grid/GridSpan.d.ts +1 -1
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +8 -2
- package/dist/Markdown/plugins/headingLinks.d.ts.map +1 -1
- package/dist/Markdown/plugins/headingLinks.js +9 -1
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +5 -0
- package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchValueEditor.js +45 -11
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +6 -2
- package/dist/Token/Token.js +1 -1
- package/dist/TopNav/TopNavMegaMenu.js +2 -0
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +2 -0
- package/dist/astryx.css +1 -1
- package/dist/reset.a11y.chromium.spec.js +50 -1
- package/docs.mjs +4 -4
- package/package.json +3 -3
- package/scripts/agent-doc-state.mjs +29 -7
- package/scripts/postinstall.mjs +43 -2
- package/src/Avatar/Avatar.tsx +11 -2
- package/src/AvatarGroup/AvatarGroupOverflow.test.tsx +24 -1
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +2 -0
- package/src/Chat/ChatLayoutScrollButton.test.tsx +13 -0
- package/src/Chat/ChatLayoutScrollButton.tsx +4 -2
- package/src/Chat/__tests__/ChatLayoutScrollButton.a11y.chromium.spec.ts +64 -19
- package/src/CheckboxList/CheckboxList.fr10.test.tsx +29 -2
- package/src/CheckboxList/CheckboxList.tsx +4 -1
- package/src/ContextMenu/ContextMenu.test.tsx +12 -2
- package/src/DropdownMenu/DropdownMenu.test.tsx +3 -4
- package/src/DropdownMenu/DropdownMenu.tsx +7 -134
- package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +1 -1
- package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +1 -1
- package/src/DropdownMenu/DropdownMenuGroup.doc.mjs +169 -10
- package/src/DropdownMenu/DropdownMenuGroup.tsx +6 -4
- package/src/DropdownMenu/DropdownMenuRadioItem.doc.mjs +1 -1
- package/src/DropdownMenu/DropdownMenuRadioItem.tsx +1 -1
- package/src/DropdownMenu/__tests__/DropdownMenuGroupAudit.a11y.chromium.spec.ts +334 -0
- package/src/DropdownMenu/menuRegionStops.ts +167 -0
- package/src/Field/FieldLabel.doc.mjs +133 -26
- package/src/Field/FieldLabel.spec.md +172 -0
- package/src/Field/__tests__/FieldLabelAudit.a11y.chromium.spec.ts +296 -0
- package/src/FormLayout/FormLayout.doc.mjs +6 -3
- package/src/FormLayout/FormLayout.spec.md +192 -0
- package/src/FormLayout/FormLayout.test.tsx +15 -0
- package/src/FormLayout/FormLayout.tsx +7 -6
- package/src/FormLayout/FormLayoutContext.ts +3 -3
- package/src/Grid/Grid.doc.mjs +7 -1
- package/src/Grid/Grid.spec.md +93 -113
- package/src/Grid/Grid.test.tsx +22 -0
- package/src/Grid/Grid.tsx +10 -9
- package/src/Grid/GridSpan.doc.mjs +3 -3
- package/src/Grid/GridSpan.tsx +1 -1
- package/src/Indicator/Indicator.doc.mjs +3 -3
- package/src/Markdown/parser.ts +8 -2
- package/src/Markdown/plugins/headingLinks.ts +9 -1
- package/src/PowerSearch/PowerSearch.test.tsx +62 -0
- package/src/PowerSearch/PowerSearch.tsx +9 -0
- package/src/PowerSearch/PowerSearchValueEditor.test.tsx +71 -0
- package/src/PowerSearch/PowerSearchValueEditor.tsx +45 -0
- package/src/Resizable/useResizable.doc.mjs +0 -1
- package/src/SideNav/SideNav.spec.md +227 -0
- package/src/SideNav/SideNav.test.tsx +41 -0
- package/src/SideNav/SideNavItem.tsx +5 -2
- package/src/Table/Table.spec.md +6 -1
- package/src/Table/plugins/activation/useTableActivation.spec.md +254 -0
- package/src/Token/Token.tsx +1 -1
- package/src/Token/__tests__/TokenRemoveTarget.a11y.chromium.spec.ts +86 -0
- package/src/Toolbar/Toolbar.test.tsx +0 -8
- package/src/Toolbar/__tests__/Toolbar.a11y.chromium.spec.ts +163 -0
- package/src/Toolbar/__tests__/Toolbar.a11y.renders.tsx +63 -0
- package/src/Toolbar/__tests__/Toolbar.a11y.states.ts +142 -0
- package/src/Toolbar/__tests__/Toolbar.a11y.test.tsx +126 -0
- package/src/TopNav/TopNavMegaMenu.test.tsx +25 -0
- package/src/TopNav/TopNavMegaMenu.tsx +2 -0
- package/src/TopNav/TopNavMenu.test.tsx +27 -1
- package/src/TopNav/TopNavMenu.tsx +2 -0
- package/src/postinstall.test.mjs +33 -0
- package/src/reset.a11y.chromium.spec.ts +52 -1
- package/src/reset.css +9 -0
- package/src/reset.test.ts +11 -0
- package/src/unicodePropertyEscapes.source-build.test.mjs +69 -0
|
@@ -80,6 +80,77 @@ function createSearchSource(
|
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
describe('isDisabled reaches every built-in value editor', () => {
|
|
84
|
+
const items = [
|
|
85
|
+
{id: 'a', label: 'Alpha'},
|
|
86
|
+
{id: 'b', label: 'Beta'},
|
|
87
|
+
];
|
|
88
|
+
const enumValues = [
|
|
89
|
+
{value: 'a', label: 'Alpha'},
|
|
90
|
+
{value: 'b', label: 'Beta'},
|
|
91
|
+
];
|
|
92
|
+
const cases: ReadonlyArray<[string, OperatorValue]> = [
|
|
93
|
+
['string', {type: 'string'}],
|
|
94
|
+
[
|
|
95
|
+
'string with a search source',
|
|
96
|
+
{type: 'string', searchSource: createSearchSource(items)},
|
|
97
|
+
],
|
|
98
|
+
[
|
|
99
|
+
'string_list',
|
|
100
|
+
{type: 'string_list', searchSource: createSearchSource(items)},
|
|
101
|
+
],
|
|
102
|
+
['integer', {type: 'integer'}],
|
|
103
|
+
['float', {type: 'float'}],
|
|
104
|
+
['time', {type: 'time'}],
|
|
105
|
+
['date_absolute', {type: 'date_absolute'}],
|
|
106
|
+
['date_relative', {type: 'date_relative'}],
|
|
107
|
+
['date_range', {type: 'date_range'}],
|
|
108
|
+
['enum', {type: 'enum', values: enumValues}],
|
|
109
|
+
['enum_list', {type: 'enum_list', values: enumValues}],
|
|
110
|
+
[
|
|
111
|
+
'entity_list',
|
|
112
|
+
{type: 'entity_list', searchSource: createSearchSource(items)},
|
|
113
|
+
],
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
// Every form control the editor renders outside a closed popover (a date
|
|
117
|
+
// input's calendar stays mounted while closed, and its disabled trigger
|
|
118
|
+
// cannot open it), so a disabled built-in editor leaves nothing a person
|
|
119
|
+
// can type into, press, or open.
|
|
120
|
+
function controlsOf(container: HTMLElement): HTMLElement[] {
|
|
121
|
+
return Array.from(
|
|
122
|
+
container.querySelectorAll<HTMLElement>(
|
|
123
|
+
'input, textarea, button, select, [role="combobox"], [contenteditable="true"]',
|
|
124
|
+
),
|
|
125
|
+
).filter(element => {
|
|
126
|
+
const popover = element.closest<HTMLElement>('[popover]');
|
|
127
|
+
return (
|
|
128
|
+
element.getAttribute('aria-hidden') !== 'true' &&
|
|
129
|
+
(popover == null || popover.matches(':popover-open'))
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const isUnavailable = (element: HTMLElement) =>
|
|
135
|
+
(element as HTMLInputElement).disabled === true ||
|
|
136
|
+
element.getAttribute('aria-disabled') === 'true';
|
|
137
|
+
|
|
138
|
+
it.each(cases)('%s', (_name, operatorValue) => {
|
|
139
|
+
const {container} = render(
|
|
140
|
+
<PowerSearchValueEditor
|
|
141
|
+
operatorValue={operatorValue}
|
|
142
|
+
filterValue={undefined}
|
|
143
|
+
onChange={() => {}}
|
|
144
|
+
config={stubConfig}
|
|
145
|
+
isDisabled
|
|
146
|
+
/>,
|
|
147
|
+
);
|
|
148
|
+
const controls = controlsOf(container);
|
|
149
|
+
expect(controls.length).toBeGreaterThan(0);
|
|
150
|
+
expect(controls.filter(element => !isUnavailable(element))).toEqual([]);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
83
154
|
// =============================================================================
|
|
84
155
|
// #1103 — StringEditor ignores searchSource
|
|
85
156
|
// =============================================================================
|
|
@@ -105,12 +105,14 @@ function StringEditor({
|
|
|
105
105
|
onChange,
|
|
106
106
|
onEnter: _onEnter,
|
|
107
107
|
maxMenuItems,
|
|
108
|
+
isDisabled,
|
|
108
109
|
}: {
|
|
109
110
|
operatorValue: OperatorValue & {type: 'string'};
|
|
110
111
|
filterValue: FilterValue | undefined;
|
|
111
112
|
onChange: (value: FilterValue, shouldSave?: boolean) => void;
|
|
112
113
|
onEnter?: () => void;
|
|
113
114
|
maxMenuItems?: number;
|
|
115
|
+
isDisabled?: boolean;
|
|
114
116
|
}) {
|
|
115
117
|
const t = useTranslator();
|
|
116
118
|
const currentValue = filterValue?.type === 'string' ? filterValue.value : '';
|
|
@@ -126,6 +128,7 @@ function StringEditor({
|
|
|
126
128
|
<Typeahead
|
|
127
129
|
label={t('@astryx.powersearch.valueEditor.value')}
|
|
128
130
|
isLabelHidden
|
|
131
|
+
isDisabled={isDisabled}
|
|
129
132
|
searchSource={operatorValue.searchSource}
|
|
130
133
|
value={selectedItem}
|
|
131
134
|
onChange={item => {
|
|
@@ -146,6 +149,7 @@ function StringEditor({
|
|
|
146
149
|
<TextInput
|
|
147
150
|
label={t('@astryx.powersearch.valueEditor.value')}
|
|
148
151
|
isLabelHidden
|
|
152
|
+
isDisabled={isDisabled}
|
|
149
153
|
value={currentValue}
|
|
150
154
|
placeholder={t('@astryx.powersearch.valueEditor.enterValuePlaceholder')}
|
|
151
155
|
onChange={(value: string) => {
|
|
@@ -160,11 +164,13 @@ function StringListEditor({
|
|
|
160
164
|
filterValue,
|
|
161
165
|
onChange,
|
|
162
166
|
maxMenuItems,
|
|
167
|
+
isDisabled,
|
|
163
168
|
}: {
|
|
164
169
|
operatorValue: OperatorValue & {type: 'string_list'};
|
|
165
170
|
filterValue: FilterValue | undefined;
|
|
166
171
|
onChange: (value: FilterValue) => void;
|
|
167
172
|
maxMenuItems?: number;
|
|
173
|
+
isDisabled?: boolean;
|
|
168
174
|
}) {
|
|
169
175
|
const t = useTranslator();
|
|
170
176
|
const currentValue: SearchableItem[] = useMemo(() => {
|
|
@@ -194,6 +200,7 @@ function StringListEditor({
|
|
|
194
200
|
<Tokenizer
|
|
195
201
|
label={t('@astryx.powersearch.valueEditor.values')}
|
|
196
202
|
isLabelHidden
|
|
203
|
+
isDisabled={isDisabled}
|
|
197
204
|
searchSource={source}
|
|
198
205
|
value={currentValue}
|
|
199
206
|
onChange={items => {
|
|
@@ -278,11 +285,13 @@ function IntegerEditor({
|
|
|
278
285
|
filterValue,
|
|
279
286
|
onChange,
|
|
280
287
|
onEnter,
|
|
288
|
+
isDisabled,
|
|
281
289
|
}: {
|
|
282
290
|
operatorValue: OperatorValue & {type: 'integer'};
|
|
283
291
|
filterValue: FilterValue | undefined;
|
|
284
292
|
onChange: (value: FilterValue, shouldSave?: boolean) => void;
|
|
285
293
|
onEnter?: () => void;
|
|
294
|
+
isDisabled?: boolean;
|
|
286
295
|
}) {
|
|
287
296
|
const t = useTranslator();
|
|
288
297
|
const currentValue =
|
|
@@ -300,6 +309,7 @@ function IntegerEditor({
|
|
|
300
309
|
<NumberInput
|
|
301
310
|
label={t('@astryx.powersearch.valueEditor.value')}
|
|
302
311
|
isLabelHidden
|
|
312
|
+
isDisabled={isDisabled}
|
|
303
313
|
value={currentValue ?? null}
|
|
304
314
|
onChange={handlers.handleChange}
|
|
305
315
|
onFocus={handlers.handleFocus}
|
|
@@ -320,11 +330,13 @@ function FloatEditor({
|
|
|
320
330
|
filterValue,
|
|
321
331
|
onChange,
|
|
322
332
|
onEnter,
|
|
333
|
+
isDisabled,
|
|
323
334
|
}: {
|
|
324
335
|
operatorValue: OperatorValue & {type: 'float'};
|
|
325
336
|
filterValue: FilterValue | undefined;
|
|
326
337
|
onChange: (value: FilterValue, shouldSave?: boolean) => void;
|
|
327
338
|
onEnter?: () => void;
|
|
339
|
+
isDisabled?: boolean;
|
|
328
340
|
}) {
|
|
329
341
|
const t = useTranslator();
|
|
330
342
|
const currentValue =
|
|
@@ -342,6 +354,7 @@ function FloatEditor({
|
|
|
342
354
|
<NumberInput
|
|
343
355
|
label={t('@astryx.powersearch.valueEditor.value')}
|
|
344
356
|
isLabelHidden
|
|
357
|
+
isDisabled={isDisabled}
|
|
345
358
|
value={currentValue ?? null}
|
|
346
359
|
onChange={handlers.handleChange}
|
|
347
360
|
onFocus={handlers.handleFocus}
|
|
@@ -360,10 +373,12 @@ function TimeEditor({
|
|
|
360
373
|
operatorValue,
|
|
361
374
|
filterValue,
|
|
362
375
|
onChange,
|
|
376
|
+
isDisabled,
|
|
363
377
|
}: {
|
|
364
378
|
operatorValue: OperatorValue & {type: 'time'};
|
|
365
379
|
filterValue: FilterValue | undefined;
|
|
366
380
|
onChange: (value: FilterValue) => void;
|
|
381
|
+
isDisabled?: boolean;
|
|
367
382
|
}) {
|
|
368
383
|
const t = useTranslator();
|
|
369
384
|
const currentValue =
|
|
@@ -375,6 +390,7 @@ function TimeEditor({
|
|
|
375
390
|
<TimeInput
|
|
376
391
|
label={t('@astryx.powersearch.valueEditor.time')}
|
|
377
392
|
isLabelHidden
|
|
393
|
+
isDisabled={isDisabled}
|
|
378
394
|
value={currentValue}
|
|
379
395
|
onChange={value => {
|
|
380
396
|
if (value != null) {
|
|
@@ -390,10 +406,12 @@ function TimeEditor({
|
|
|
390
406
|
function DateAbsoluteEditor({
|
|
391
407
|
filterValue,
|
|
392
408
|
onChange,
|
|
409
|
+
isDisabled,
|
|
393
410
|
}: {
|
|
394
411
|
operatorValue: OperatorValue & {type: 'date_absolute'};
|
|
395
412
|
filterValue: FilterValue | undefined;
|
|
396
413
|
onChange: (value: FilterValue) => void;
|
|
414
|
+
isDisabled?: boolean;
|
|
397
415
|
}) {
|
|
398
416
|
const t = useTranslator();
|
|
399
417
|
// Convert unixSeconds to ISO date string for the date input
|
|
@@ -409,6 +427,7 @@ function DateAbsoluteEditor({
|
|
|
409
427
|
<DateInput
|
|
410
428
|
label={t('@astryx.powersearch.valueEditor.date')}
|
|
411
429
|
isLabelHidden
|
|
430
|
+
isDisabled={isDisabled}
|
|
412
431
|
value={currentValue}
|
|
413
432
|
onChange={value => {
|
|
414
433
|
if (value != null) {
|
|
@@ -424,10 +443,12 @@ function DateRelativeEditor({
|
|
|
424
443
|
operatorValue,
|
|
425
444
|
filterValue,
|
|
426
445
|
onChange,
|
|
446
|
+
isDisabled,
|
|
427
447
|
}: {
|
|
428
448
|
operatorValue: OperatorValue & {type: 'date_relative'};
|
|
429
449
|
filterValue: FilterValue | undefined;
|
|
430
450
|
onChange: (value: FilterValue, shouldSave?: boolean) => void;
|
|
451
|
+
isDisabled?: boolean;
|
|
431
452
|
}) {
|
|
432
453
|
const t = useTranslator();
|
|
433
454
|
const currentValue =
|
|
@@ -469,6 +490,7 @@ function DateRelativeEditor({
|
|
|
469
490
|
<Selector
|
|
470
491
|
label={t('@astryx.powersearch.valueEditor.relativeDate')}
|
|
471
492
|
isLabelHidden
|
|
493
|
+
isDisabled={isDisabled}
|
|
472
494
|
options={options}
|
|
473
495
|
value={currentValue}
|
|
474
496
|
onChange={value => {
|
|
@@ -481,10 +503,12 @@ function DateRelativeEditor({
|
|
|
481
503
|
function DateRangeEditor({
|
|
482
504
|
filterValue,
|
|
483
505
|
onChange,
|
|
506
|
+
isDisabled,
|
|
484
507
|
}: {
|
|
485
508
|
operatorValue: OperatorValue & {type: 'date_range'};
|
|
486
509
|
filterValue: FilterValue | undefined;
|
|
487
510
|
onChange: (value: FilterValue) => void;
|
|
511
|
+
isDisabled?: boolean;
|
|
488
512
|
}) {
|
|
489
513
|
const t = useTranslator();
|
|
490
514
|
const currentValue = useMemo<DateRange | null>(() => {
|
|
@@ -534,6 +558,7 @@ function DateRangeEditor({
|
|
|
534
558
|
<DateRangeInput
|
|
535
559
|
label={t('@astryx.powersearch.valueEditor.dateRange')}
|
|
536
560
|
isLabelHidden
|
|
561
|
+
isDisabled={isDisabled}
|
|
537
562
|
value={currentValue}
|
|
538
563
|
onChange={handleChange}
|
|
539
564
|
hasClear={false}
|
|
@@ -546,10 +571,12 @@ function EnumEditor({
|
|
|
546
571
|
operatorValue,
|
|
547
572
|
filterValue,
|
|
548
573
|
onChange,
|
|
574
|
+
isDisabled,
|
|
549
575
|
}: {
|
|
550
576
|
operatorValue: OperatorValue & {type: 'enum'};
|
|
551
577
|
filterValue: FilterValue | undefined;
|
|
552
578
|
onChange: (value: FilterValue, shouldSave?: boolean) => void;
|
|
579
|
+
isDisabled?: boolean;
|
|
553
580
|
}) {
|
|
554
581
|
const t = useTranslator();
|
|
555
582
|
const currentValue =
|
|
@@ -568,6 +595,7 @@ function EnumEditor({
|
|
|
568
595
|
<Selector
|
|
569
596
|
label={t('@astryx.powersearch.valueEditor.value')}
|
|
570
597
|
isLabelHidden
|
|
598
|
+
isDisabled={isDisabled}
|
|
571
599
|
options={options}
|
|
572
600
|
value={currentValue}
|
|
573
601
|
onChange={value => {
|
|
@@ -581,10 +609,12 @@ function EnumListEditor({
|
|
|
581
609
|
operatorValue,
|
|
582
610
|
filterValue,
|
|
583
611
|
onChange,
|
|
612
|
+
isDisabled,
|
|
584
613
|
}: {
|
|
585
614
|
operatorValue: OperatorValue & {type: 'enum_list'};
|
|
586
615
|
filterValue: FilterValue | undefined;
|
|
587
616
|
onChange: (value: FilterValue) => void;
|
|
617
|
+
isDisabled?: boolean;
|
|
588
618
|
}) {
|
|
589
619
|
const t = useTranslator();
|
|
590
620
|
const items = useMemo(
|
|
@@ -608,6 +638,7 @@ function EnumListEditor({
|
|
|
608
638
|
<Tokenizer
|
|
609
639
|
label={t('@astryx.powersearch.valueEditor.values')}
|
|
610
640
|
isLabelHidden
|
|
641
|
+
isDisabled={isDisabled}
|
|
611
642
|
searchSource={source}
|
|
612
643
|
value={currentValue}
|
|
613
644
|
onChange={selectedItems => {
|
|
@@ -632,11 +663,13 @@ function EntityListEditor({
|
|
|
632
663
|
filterValue,
|
|
633
664
|
onChange,
|
|
634
665
|
maxMenuItems,
|
|
666
|
+
isDisabled,
|
|
635
667
|
}: {
|
|
636
668
|
operatorValue: OperatorValue & {type: 'entity_list'};
|
|
637
669
|
filterValue: FilterValue | undefined;
|
|
638
670
|
onChange: (value: FilterValue) => void;
|
|
639
671
|
maxMenuItems?: number;
|
|
672
|
+
isDisabled?: boolean;
|
|
640
673
|
}) {
|
|
641
674
|
const t = useTranslator();
|
|
642
675
|
const source = useMemo<SearchSource<SearchableItem>>(() => {
|
|
@@ -665,6 +698,7 @@ function EntityListEditor({
|
|
|
665
698
|
<Tokenizer
|
|
666
699
|
label={t('@astryx.powersearch.valueEditor.entities')}
|
|
667
700
|
isLabelHidden
|
|
701
|
+
isDisabled={isDisabled}
|
|
668
702
|
searchSource={source}
|
|
669
703
|
value={currentValue}
|
|
670
704
|
onChange={items => {
|
|
@@ -738,6 +772,7 @@ export function PowerSearchValueEditor({
|
|
|
738
772
|
case 'string':
|
|
739
773
|
return (
|
|
740
774
|
<StringEditor
|
|
775
|
+
isDisabled={isDisabled}
|
|
741
776
|
operatorValue={operatorValue}
|
|
742
777
|
filterValue={filterValue}
|
|
743
778
|
onChange={onChange}
|
|
@@ -749,6 +784,7 @@ export function PowerSearchValueEditor({
|
|
|
749
784
|
case 'string_list':
|
|
750
785
|
return (
|
|
751
786
|
<StringListEditor
|
|
787
|
+
isDisabled={isDisabled}
|
|
752
788
|
operatorValue={operatorValue}
|
|
753
789
|
filterValue={filterValue}
|
|
754
790
|
onChange={onChange}
|
|
@@ -759,6 +795,7 @@ export function PowerSearchValueEditor({
|
|
|
759
795
|
case 'integer':
|
|
760
796
|
return (
|
|
761
797
|
<IntegerEditor
|
|
798
|
+
isDisabled={isDisabled}
|
|
762
799
|
operatorValue={operatorValue}
|
|
763
800
|
filterValue={filterValue}
|
|
764
801
|
onChange={onChange}
|
|
@@ -769,6 +806,7 @@ export function PowerSearchValueEditor({
|
|
|
769
806
|
case 'float':
|
|
770
807
|
return (
|
|
771
808
|
<FloatEditor
|
|
809
|
+
isDisabled={isDisabled}
|
|
772
810
|
operatorValue={operatorValue}
|
|
773
811
|
filterValue={filterValue}
|
|
774
812
|
onChange={onChange}
|
|
@@ -779,6 +817,7 @@ export function PowerSearchValueEditor({
|
|
|
779
817
|
case 'time':
|
|
780
818
|
return (
|
|
781
819
|
<TimeEditor
|
|
820
|
+
isDisabled={isDisabled}
|
|
782
821
|
operatorValue={operatorValue}
|
|
783
822
|
filterValue={filterValue}
|
|
784
823
|
onChange={onChange}
|
|
@@ -788,6 +827,7 @@ export function PowerSearchValueEditor({
|
|
|
788
827
|
case 'date_absolute':
|
|
789
828
|
return (
|
|
790
829
|
<DateAbsoluteEditor
|
|
830
|
+
isDisabled={isDisabled}
|
|
791
831
|
operatorValue={operatorValue}
|
|
792
832
|
filterValue={filterValue}
|
|
793
833
|
onChange={onChange}
|
|
@@ -797,6 +837,7 @@ export function PowerSearchValueEditor({
|
|
|
797
837
|
case 'date_relative':
|
|
798
838
|
return (
|
|
799
839
|
<DateRelativeEditor
|
|
840
|
+
isDisabled={isDisabled}
|
|
800
841
|
operatorValue={operatorValue}
|
|
801
842
|
filterValue={filterValue}
|
|
802
843
|
onChange={onChange}
|
|
@@ -806,6 +847,7 @@ export function PowerSearchValueEditor({
|
|
|
806
847
|
case 'date_range':
|
|
807
848
|
return (
|
|
808
849
|
<DateRangeEditor
|
|
850
|
+
isDisabled={isDisabled}
|
|
809
851
|
operatorValue={operatorValue}
|
|
810
852
|
filterValue={filterValue}
|
|
811
853
|
onChange={onChange}
|
|
@@ -815,6 +857,7 @@ export function PowerSearchValueEditor({
|
|
|
815
857
|
case 'enum':
|
|
816
858
|
return (
|
|
817
859
|
<EnumEditor
|
|
860
|
+
isDisabled={isDisabled}
|
|
818
861
|
operatorValue={operatorValue}
|
|
819
862
|
filterValue={filterValue}
|
|
820
863
|
onChange={onChange}
|
|
@@ -824,6 +867,7 @@ export function PowerSearchValueEditor({
|
|
|
824
867
|
case 'enum_list':
|
|
825
868
|
return (
|
|
826
869
|
<EnumListEditor
|
|
870
|
+
isDisabled={isDisabled}
|
|
827
871
|
operatorValue={operatorValue}
|
|
828
872
|
filterValue={filterValue}
|
|
829
873
|
onChange={onChange}
|
|
@@ -833,6 +877,7 @@ export function PowerSearchValueEditor({
|
|
|
833
877
|
case 'entity_list':
|
|
834
878
|
return (
|
|
835
879
|
<EntityListEditor
|
|
880
|
+
isDisabled={isDisabled}
|
|
836
881
|
operatorValue={operatorValue}
|
|
837
882
|
filterValue={filterValue}
|
|
838
883
|
onChange={onChange}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema_version: 3
|
|
3
|
+
template_version: 7
|
|
4
|
+
kind: component
|
|
5
|
+
id: component:SideNav
|
|
6
|
+
authority: current
|
|
7
|
+
archive_reason: null
|
|
8
|
+
superseded_by: null
|
|
9
|
+
approved_by: cixzhang
|
|
10
|
+
approved_at: 2026-10-16
|
|
11
|
+
owners: [cixzhang]
|
|
12
|
+
review_triggers: [layout, behavior, accessibility]
|
|
13
|
+
verified_by:
|
|
14
|
+
[
|
|
15
|
+
packages/core/src/SideNav/SideNav.test.tsx,
|
|
16
|
+
apps/storybook/stories/SideNav.stories.tsx,
|
|
17
|
+
scripts/check-knowledge.mjs,
|
|
18
|
+
]
|
|
19
|
+
modules: []
|
|
20
|
+
families: [family:navigation-destinations]
|
|
21
|
+
design_specs: []
|
|
22
|
+
architecture:
|
|
23
|
+
[
|
|
24
|
+
architecture:component-theming-surface,
|
|
25
|
+
architecture:interaction-modality,
|
|
26
|
+
architecture:internationalization,
|
|
27
|
+
architecture:public-component-api,
|
|
28
|
+
]
|
|
29
|
+
contributing: []
|
|
30
|
+
system_specs: [spec:AST-017/FR3]
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
# SideNav component contract
|
|
34
|
+
|
|
35
|
+
<!-- Describe the system, not the project: present tense, what it does. No proposals, history, pull requests, or research in the record; see docs/contributing/spec-writing.md and report its rubric results in the pull request. -->
|
|
36
|
+
|
|
37
|
+
This record governs how nested SideNavItem rows are drawn: their width, where
|
|
38
|
+
their nesting indent sits, and what the indent area does. It covers SideNav,
|
|
39
|
+
SideNavSection, and SideNavItem only as far as that geometry depends on them.
|
|
40
|
+
SideNavItem's link and destination safety is governed by
|
|
41
|
+
`family:navigation-destinations`; other SideNav behavior remains described by
|
|
42
|
+
its source, tests, and consumer docs until a record claims it.
|
|
43
|
+
|
|
44
|
+
## Contract at a glance
|
|
45
|
+
|
|
46
|
+
| Area | Contract |
|
|
47
|
+
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
48
|
+
| Public contract | None. No prop, value, default, or theme target changes. |
|
|
49
|
+
| Behavior | Every SideNavItem row spans the full width of its nav list at any depth. Depth shows as inline-start indent inside the row: one `--spacing-6` step per level, added to the row's own padding. |
|
|
50
|
+
| End-user impact | A person scanning a nested nav sees hover, pressed, selected, and focus fills that line up with the top-level rows, and can click a nested row's indent to activate it, as they would its label. |
|
|
51
|
+
| Builder impact | No new choice or prop. Icons and labels keep their per-depth position. Content placed directly inside a SideNavItem that is not itself a SideNavItem sits at the nav list edge, because the nested group adds no inset; no supported setting insets it. |
|
|
52
|
+
| Compatibility/readiness | Nonbreaking change of visual direction (`spec:AST-017/FR3`). The collapsed rail and top-level rows are unchanged. Authority is current; implementation may be staged. |
|
|
53
|
+
| Review checks | Reject a nested row narrower than a top-level row, an indent drawn outside the row, an indent area that shows feedback but does not activate, a clipped focus ring, or an indent in the flyout's top level. |
|
|
54
|
+
| Governing rules | DD1 and FR3–FR5 below; `family:navigation-destinations` for destinations; `architecture:internationalization` for logical direction. |
|
|
55
|
+
|
|
56
|
+
This table is a review projection; the body below is authoritative.
|
|
57
|
+
|
|
58
|
+
## Intent
|
|
59
|
+
|
|
60
|
+
SideNav is the vertical navigation region of an app. SideNavItem rows can nest
|
|
61
|
+
to show a hierarchy of destinations. Nesting must read as one list of equal
|
|
62
|
+
rows, so the eye compares labels and states across depths rather than seeing a
|
|
63
|
+
second, narrower kind of row. SideNav owns this because only SideNavItem knows
|
|
64
|
+
a row's depth and its row padding.
|
|
65
|
+
|
|
66
|
+
## Compatibility and migration
|
|
67
|
+
|
|
68
|
+
- Released default preserved: `no`; nested rows change from inset to full width.
|
|
69
|
+
Top-level rows and the collapsed rail are unchanged.
|
|
70
|
+
- Compatibility class: nonbreaking appearance change of visual direction under
|
|
71
|
+
`spec:AST-017/FR3`; no public API, accepted input, or accessibility semantic
|
|
72
|
+
changes.
|
|
73
|
+
- Controlled/uncontrolled behavior: unchanged.
|
|
74
|
+
- Migration decision: none; there is no supported way to keep the inset look.
|
|
75
|
+
|
|
76
|
+
Consumer migration instructions belong in consumer docs and release notes.
|
|
77
|
+
|
|
78
|
+
## Ownership boundary
|
|
79
|
+
|
|
80
|
+
**Owns**
|
|
81
|
+
|
|
82
|
+
- The width of every expanded SideNavItem row, at every depth.
|
|
83
|
+
- Where a nested row's indent sits, its step, and how it adds to the row's
|
|
84
|
+
padding.
|
|
85
|
+
- What the indent area paints and what a click on it does.
|
|
86
|
+
- Whether the nested group clips its rows.
|
|
87
|
+
|
|
88
|
+
**Does not own / non-goals**
|
|
89
|
+
|
|
90
|
+
- Destination acceptance, blocking, and router handoff — owned by
|
|
91
|
+
`family:navigation-destinations`.
|
|
92
|
+
- The collapsed item's tooltip and how it attaches — outside this record.
|
|
93
|
+
- Exact fill colors, radius, row height, and row padding per size — owned by
|
|
94
|
+
the shared nav item styles and the `side-nav-item` theme target.
|
|
95
|
+
- Layout of caller content placed directly inside a SideNavItem that is not a
|
|
96
|
+
SideNavItem — owned by the product callsite.
|
|
97
|
+
- SideNavHeading, SideNavCollapseButton, and the SideNav header, footer, and
|
|
98
|
+
resize zones.
|
|
99
|
+
|
|
100
|
+
## Public concepts
|
|
101
|
+
|
|
102
|
+
| Concept | Closed values or states | Meaning | Availability by variant/orientation/state | Default | Owner | Stability | Invalid-value behavior |
|
|
103
|
+
| ------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ----------------------------------------- | ------- | ------------------- | --------- | ---------------------- |
|
|
104
|
+
| Nesting depth | `0`, `1`, `2`, … (derived, not a prop) | The number of SideNavItem ancestors of a row within the same list. A collapsed rail's flyout restarts at 0. | Expanded rail; mobile drawer; flyout | `0` | `component:SideNav` | stable | Not caller-settable |
|
|
105
|
+
| Row size | `sm`, `md`, `lg` (existing SideNavItem `size`) | Sets the row's height and base inline padding; the indent adds to that padding. | Every row | `md` | `component:SideNav` | stable | Rejected by the type |
|
|
106
|
+
|
|
107
|
+
## Behavioral and layout contract
|
|
108
|
+
|
|
109
|
+
| ID | Invariant | Basis | Draft review state |
|
|
110
|
+
| --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | -------------------------------------------- |
|
|
111
|
+
| FR1 | SideNav renders its navigation children in a scrollable list, which may be grouped by SideNavSection. A SideNavItem with children renders them, after its own row, in a group labelled by the parent's label; unless `collapsible` is `false`, the group expands and collapses and is hidden from assistive technology and inert while collapsed. | Current source, docs, and tests | Current behavior |
|
|
112
|
+
| FR2 | In a collapsed rail, a SideNavItem without an icon is not rendered; a leaf item renders icon-only, named by a non-blank caller `aria-label` or else its label; an item with children renders an icon-only trigger that opens a flyout listing its children as the flyout's top level. | Current source, docs, and tests | Current behavior |
|
|
113
|
+
| FR3 | In an expanded rail, in a flyout, and in the mobile navigation drawer, every SideNavItem row MUST span the full inline size of its list at any depth. A row at depth `n` MUST start its content `n` × `--spacing-6` beyond the row's own inline-start padding for its size, inside the row; the nested group MUST add no inset. A flyout's top level is depth 0. | `component:SideNav/DEC-1` | Settled intent; implementation may be staged |
|
|
114
|
+
| FR4 | The indent area MUST be part of the row's target: a click there, including a modified or middle click on a link row, does what the same click on the row's primary element does. When the row also carries an independent expand toggle or `actions`, the indent belongs to the primary link or button, and the toggle and actions keep their own activation at the row's trailing edge. | `component:SideNav/DEC-1`; `family:navigation-destinations` | Settled intent; implementation may be staged |
|
|
115
|
+
| FR5 | An expanded nested group MUST NOT clip its rows once it has finished opening, so a nested row's focus ring paints in full. The group MAY clip while collapsed and while it opens or closes. | `component:SideNav/DEC-1` | Settled intent; implementation may be staged |
|
|
116
|
+
|
|
117
|
+
### Allowed variation
|
|
118
|
+
|
|
119
|
+
- **AV1 — Depth and size.** Any depth and any row size may appear; FR3 holds for
|
|
120
|
+
each row from its own size's padding.
|
|
121
|
+
- **AV2 — Theme tokens.** A theme that changes `--spacing-6` or the row padding
|
|
122
|
+
tokens changes the step or the base with them.
|
|
123
|
+
- **AV3 — Row implementation.** The element that carries the indent may change
|
|
124
|
+
while the painted row stays full width and the indent stays clickable.
|
|
125
|
+
|
|
126
|
+
### Representative states
|
|
127
|
+
|
|
128
|
+
| State | Required invariant | Allowed variation |
|
|
129
|
+
| ------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------- |
|
|
130
|
+
| Depth 1–3, `md` | Row width equals a top-level row's; content starts one step further in per level. | Labels, icons, selection. |
|
|
131
|
+
| `sm` and `lg` nested rows | Indent adds to that size's base padding. | Row height by size. |
|
|
132
|
+
| Nested row with own link and children | Full-width row; indent is part of the link; the expand toggle stays at the trailing edge. | Collapsed or expanded children. |
|
|
133
|
+
| Nested row with `actions` | Full-width row; indent is part of the primary element; actions stay at the trailing edge. | Action content. |
|
|
134
|
+
| Keyboard focus on a nested row | The focus ring paints in full around the row. | Ring style by theme. |
|
|
135
|
+
| Right-to-left | Indent sits at the inline start (right); same widths. | Writing direction. |
|
|
136
|
+
| Collapsed rail | Icon-only items; the flyout's top level has no indent; deeper flyout levels indent. | Flyout content. |
|
|
137
|
+
|
|
138
|
+
### Transformation and precedence order
|
|
139
|
+
|
|
140
|
+
- **ORD1 — Row inline-start padding.** The row's size padding, then plus depth ×
|
|
141
|
+
`--spacing-6`. A caller's `style` on a nested item composes after the indent in the
|
|
142
|
+
shared order (`architecture:public-component-api`): properties the caller does
|
|
143
|
+
not set keep the indent, and a caller-set inline-start padding replaces it.
|
|
144
|
+
|
|
145
|
+
### Performance and resources
|
|
146
|
+
|
|
147
|
+
- **PR1 — No measurement.** Depth and indent come from composition; no
|
|
148
|
+
measurement, observer, or extra render pass is added.
|
|
149
|
+
|
|
150
|
+
## Accessibility contract
|
|
151
|
+
|
|
152
|
+
- **AR1 — Presentation only.** Width and indent MUST NOT change any row's role,
|
|
153
|
+
accessible name, state, DOM order, or tab order.
|
|
154
|
+
- **AR2 — Visible focus.** A nested row's focus indicator MUST paint in full at
|
|
155
|
+
every depth (FR5).
|
|
156
|
+
|
|
157
|
+
## Design relationships
|
|
158
|
+
|
|
159
|
+
| Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
|
|
160
|
+
| ---------------- | ------------------------------------------------------------------ | ------------------------ | -------------- | ------------------ |
|
|
161
|
+
| Navigation items | Nested rows read as equal rows of one list; depth reads as indent. | Owner choice (DD1) | Prominent | FR3–FR5 |
|
|
162
|
+
|
|
163
|
+
### Design decisions
|
|
164
|
+
|
|
165
|
+
<!-- design-decisions:v1 -->
|
|
166
|
+
|
|
167
|
+
| ID | Decision | Intent or reason | Applies to | Allowed variation |
|
|
168
|
+
| --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
169
|
+
| DD1 | Every SideNavItem row spans the full width of its nav list at any depth; nesting shows as indent inside the row, ahead of its content. Hover, pressed, selected, and focus paint the whole row, indent included. | Fills line up across depths, so the nav reads as one list of equal rows while labels keep their per-level alignment; the indent belongs to the row's primary target rather than sitting beside it as a gap. | SideNavItem rows at every depth, at rest and through hover, pressed, selected, disabled, and focus | Themes restyle fill, radius, and padding through the `side-nav-item` target; the step follows `--spacing-6`. |
|
|
170
|
+
|
|
171
|
+
## Family and system relationships
|
|
172
|
+
|
|
173
|
+
- `family:navigation-destinations` owns the accept/block result for a
|
|
174
|
+
SideNavItem `href`; the indent area activates the same destination the primary
|
|
175
|
+
element does (FR4).
|
|
176
|
+
- `architecture:internationalization` owns logical direction; the indent is
|
|
177
|
+
inline-start and mirrors under right-to-left.
|
|
178
|
+
- `architecture:interaction-modality` owns shared focus-visible behavior; FR5
|
|
179
|
+
keeps the ring visible at every depth.
|
|
180
|
+
- `architecture:component-theming-surface` owns the `side-nav-item` target;
|
|
181
|
+
this record adds no target or state.
|
|
182
|
+
- `architecture:public-component-api` owns how caller styling inputs compose;
|
|
183
|
+
ORD1 places the indent before a caller's `style`.
|
|
184
|
+
- `spec:AST-017/FR3` classifies the change as a nonbreaking change of visual
|
|
185
|
+
direction.
|
|
186
|
+
- `component:TreeList` DD1–DD3 record the same row geometry for TreeList. They
|
|
187
|
+
are precedent, not authority here; each component owns its own rows.
|
|
188
|
+
|
|
189
|
+
## Verification map
|
|
190
|
+
|
|
191
|
+
| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
|
|
192
|
+
| --------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
|
|
193
|
+
| FR1, FR2 | `SideNav.test.tsx` nesting, collapsible, and collapsed-rail suites | Nested, collapsible, collapsed rail, flyout | Removing the labelled group, its collapsed inert state, or the collapsed icon-only and flyout paths fails. | `audit:SideNav/anatomy` |
|
|
194
|
+
| FR3, ORD1 | Focused `SideNav.test.tsx` indent coverage, required with implementation | Depth 1 and 2; `sm` and `md`; split and actions rows; caller `style`; flyout | A group inset, a missing or wrong indent, a dropped caller `style`, an indent that overrides a caller-set inline-start padding, or an indented flyout top level fails. | `audit:SideNav/layout` |
|
|
195
|
+
| FR3, DD1 | Real-browser geometry over the SideNav nested stories | Depths 1–3; `sm`, `md`, `lg`; light and dark; right-to-left; mobile drawer | A nested row's box narrower than a top-level row's, or a label moved from its per-depth position, fails. | `audit:SideNav/layout` |
|
|
196
|
+
| FR4 | Real-browser click in the indent area of plain, split, and actions rows | Link and button rows; plain, modified, and middle clicks; nested rows with toggle and actions | A click in the indent that does nothing or differs from the same click on the primary element, or a toggle or action that activates the row, fails. | `audit:SideNav/behavior` |
|
|
197
|
+
| FR5, AR2 | `SideNav.test.tsx` group clipping case and real-browser keyboard focus on nested rows | Only-child and last nested rows; after opening; collapsed | A focus ring cut at any edge of an expanded group, or an expanded group that still clips, fails. | `audit:SideNav/a11y` |
|
|
198
|
+
| FR2, FR3 | Real-browser collapsed rail and flyout | Collapsed rail with nested children | Any change to the collapsed rail, or an indent on the flyout's top level, fails. | `audit:SideNav/layout` |
|
|
199
|
+
| AR1 | Accessibility-tree comparison of nested rows | Plain, split, and actions rows | Any change in role, name, state, or order fails. | `audit:SideNav/a11y` |
|
|
200
|
+
|
|
201
|
+
## Decision log
|
|
202
|
+
|
|
203
|
+
<!-- Record the boundary or requirement, not how it was reached. A rejected alternative is at most one line here, kept only when it is consequential and likely to recur. -->
|
|
204
|
+
|
|
205
|
+
### DEC-1 — Nested rows are full width; depth is indent inside the row
|
|
206
|
+
|
|
207
|
+
**Reference:** `component:SideNav/DEC-1`
|
|
208
|
+
**Decider:** cixzhang, 2026-10-16
|
|
209
|
+
|
|
210
|
+
A nested row that starts at its indent paints its fills from a different edge
|
|
211
|
+
than the rows above it, so it reads as a different, narrower component. A row that
|
|
212
|
+
spans the full width with its indent inside lines up every fill and keeps each
|
|
213
|
+
label at its per-depth position. The indent is part of the row's primary
|
|
214
|
+
target, so it acts as well as lights up. Full-width rows reach the nested group's
|
|
215
|
+
edges, so an open group must not clip them, or their focus ring is cut off.
|
|
216
|
+
|
|
217
|
+
Rejected: indenting the nested group from outside the rows, because fills then
|
|
218
|
+
start at a different edge per depth.
|
|
219
|
+
|
|
220
|
+
## Open questions
|
|
221
|
+
|
|
222
|
+
None.
|
|
223
|
+
|
|
224
|
+
## Content boundary
|
|
225
|
+
|
|
226
|
+
This file does not duplicate consumer prop tables/examples, current audit
|
|
227
|
+
results, implementation steps, or family/system rules. It links to their owners.
|