@astryxdesign/core 0.7.8-canary.d3ec2ef → 0.7.8-canary.e4c81f3
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/CheckboxList/CheckboxList.d.ts +13 -2
- package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxList.js +10 -4
- package/dist/CheckboxList/CheckboxListContext.d.ts +7 -0
- package/dist/CheckboxList/CheckboxListContext.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListContext.js +2 -1
- package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.js +14 -2
- package/dist/CheckboxList/requiredGroup.d.ts +22 -0
- package/dist/CheckboxList/requiredGroup.d.ts.map +1 -0
- package/dist/CheckboxList/requiredGroup.js +52 -0
- package/dist/utils/sharedMutationObserver.d.ts +2 -1
- package/dist/utils/sharedMutationObserver.d.ts.map +1 -1
- package/dist/utils/sharedMutationObserver.js +2 -1
- package/package.json +4 -4
- package/src/CheckboxList/CheckboxList.doc.mjs +7 -0
- package/src/CheckboxList/CheckboxList.tsx +34 -2
- package/src/CheckboxList/CheckboxListContext.tsx +9 -1
- package/src/CheckboxList/CheckboxListItem.tsx +23 -2
- package/src/CheckboxList/requiredGroup.ts +77 -0
- package/src/utils/sharedMutationObserver.ts +2 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file CheckboxList.tsx
|
|
3
|
-
* @input Uses React, useId, useOptimistic, useTransition, Field, List, CheckboxListContext
|
|
3
|
+
* @input Uses React, useId, useOptimistic, useTransition, Field, List, CheckboxListContext, requiredGroup
|
|
4
4
|
* @output Exports CheckboxList component, CheckboxListProps
|
|
5
5
|
* @position Core implementation; consumed by index.ts, tested by CheckboxList.test.tsx
|
|
6
6
|
*
|
|
@@ -39,6 +39,17 @@ export interface CheckboxListProps extends Omit<FieldInputBaseProps<HTMLDivEleme
|
|
|
39
39
|
* @default false
|
|
40
40
|
*/
|
|
41
41
|
isOptional?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Whether the group needs at least one checked box. Shows the required
|
|
44
|
+
* indicator after the group label by the same rules as every input field.
|
|
45
|
+
* While no box is checked, the browser blocks submit and moves focus to
|
|
46
|
+
* the first box that is neither disabled nor read-only. A disabled or
|
|
47
|
+
* read-only group requires nothing. Show your own error text through
|
|
48
|
+
* `status`.
|
|
49
|
+
* Mutually exclusive with isOptional.
|
|
50
|
+
* @default false
|
|
51
|
+
*/
|
|
52
|
+
isRequired?: boolean;
|
|
42
53
|
/**
|
|
43
54
|
* Status indicator for the checkbox group.
|
|
44
55
|
* When set with a message, displays a colored message box below the group.
|
|
@@ -122,7 +133,7 @@ export interface CheckboxListProps extends Omit<FieldInputBaseProps<HTMLDivEleme
|
|
|
122
133
|
* </CheckboxList>
|
|
123
134
|
* ```
|
|
124
135
|
*/
|
|
125
|
-
export declare function CheckboxList({ label, isLabelHidden, description, isOptional, status, value, onChange, changeAction, density, hasDividers, isDisabled, disabledMessage, isReadOnly, children, ref, width, containerXstyle, containerClassName, containerStyle, controlXstyle, controlClassName, controlStyle, 'aria-labelledby': ariaLabelledByProp, 'aria-describedby': ariaDescribedByProp, ...restProps }: CheckboxListProps): import("react").JSX.Element;
|
|
136
|
+
export declare function CheckboxList({ label, isLabelHidden, description, isOptional, isRequired, status, value, onChange, changeAction, density, hasDividers, isDisabled, disabledMessage, isReadOnly, children, ref, width, containerXstyle, containerClassName, containerStyle, controlXstyle, controlClassName, controlStyle, 'aria-labelledby': ariaLabelledByProp, 'aria-describedby': ariaDescribedByProp, ...restProps }: CheckboxListProps): import("react").JSX.Element;
|
|
126
137
|
export declare namespace CheckboxList {
|
|
127
138
|
var displayName: string;
|
|
128
139
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CheckboxList.d.ts","sourceRoot":"","sources":["../../src/CheckboxList/CheckboxList.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAML,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,EACL,KAAK,mBAAmB,EAEzB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAC;AAE9C,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,gBAAgB,CAAC;AAEhD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"CheckboxList.d.ts","sourceRoot":"","sources":["../../src/CheckboxList/CheckboxList.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAML,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,EACL,KAAK,mBAAmB,EAEzB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAC;AAE9C,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,gBAAgB,CAAC;AAEhD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,qBAAqB,CAAC;AAcrD,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAC7C,mBAAmB,CAAC,cAAc,CAAC,EACnC,UAAU,CACX;IACC,mDAAmD;IACnD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;;OASG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACtC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D;;;OAGG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;;;OAUG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,aAAqB,EACrB,WAAW,EACX,UAAkB,EAClB,UAAkB,EAClB,MAAM,EACN,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,OAAoB,EACpB,WAAmB,EACnB,UAAkB,EAClB,eAAe,EACf,UAAkB,EAClB,QAAQ,EACR,GAAG,EACH,KAAK,EACL,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,iBAAiB,EAAE,kBAAkB,EACrC,kBAAkB,EAAE,mBAAmB,EACvC,GAAG,SAAS,EACb,EAAE,iBAAiB,+BA2KnB;yBAtMe,YAAY"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file CheckboxList.tsx
|
|
7
|
-
* @input Uses React, useId, useOptimistic, useTransition, Field, List, CheckboxListContext
|
|
7
|
+
* @input Uses React, useId, useOptimistic, useTransition, Field, List, CheckboxListContext, requiredGroup
|
|
8
8
|
* @output Exports CheckboxList component, CheckboxListProps
|
|
9
9
|
* @position Core implementation; consumed by index.ts, tested by CheckboxList.test.tsx
|
|
10
10
|
*
|
|
@@ -26,6 +26,7 @@ import { mergeProps } from "../utils/index.js";
|
|
|
26
26
|
import { themeProps } from "../utils/themeProps.js";
|
|
27
27
|
import { joinAriaIDs } from "../utils/inputAria.js";
|
|
28
28
|
import { CheckboxListContext } from "./CheckboxListContext.js";
|
|
29
|
+
import { createRequiredGroup } from "./requiredGroup.js";
|
|
29
30
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
30
31
|
const EMPTY_ARRAY = [];
|
|
31
32
|
/**
|
|
@@ -51,6 +52,7 @@ export function CheckboxList({
|
|
|
51
52
|
isLabelHidden = false,
|
|
52
53
|
description,
|
|
53
54
|
isOptional = false,
|
|
55
|
+
isRequired = false,
|
|
54
56
|
status,
|
|
55
57
|
value,
|
|
56
58
|
onChange,
|
|
@@ -85,6 +87,8 @@ export function CheckboxList({
|
|
|
85
87
|
const labelID = useId();
|
|
86
88
|
const descriptionID = useId();
|
|
87
89
|
const statusMessageID = useId();
|
|
90
|
+
const requiredGroup = useMemo(() => isRequired ? createRequiredGroup() : undefined, [isRequired]);
|
|
91
|
+
const observeRequiredGroup = useCallback(container => container != null && requiredGroup != null ? requiredGroup.observe(container) : undefined, [requiredGroup]);
|
|
88
92
|
const [, startTransition] = useTransition();
|
|
89
93
|
const isCollectionMode = value !== undefined;
|
|
90
94
|
const effectiveValue = value ?? EMPTY_ARRAY;
|
|
@@ -128,8 +132,9 @@ export function CheckboxList({
|
|
|
128
132
|
disabledMessageID: showsDisabledMessage ? disabledMessageTooltip.describedBy : undefined,
|
|
129
133
|
isReadOnly,
|
|
130
134
|
isInvalid: status?.type === 'error',
|
|
131
|
-
loadingValues
|
|
132
|
-
|
|
135
|
+
loadingValues,
|
|
136
|
+
requiredGroup
|
|
137
|
+
}), [isCollectionMode, optimisticValue, handleChange, isDisabled, showsDisabledMessage, disabledMessageTooltip.describedBy, isReadOnly, status?.type, loadingValues, requiredGroup]);
|
|
133
138
|
return /*#__PURE__*/_jsxs(Field, {
|
|
134
139
|
label: label,
|
|
135
140
|
isLabelHidden: isLabelHidden,
|
|
@@ -138,6 +143,7 @@ export function CheckboxList({
|
|
|
138
143
|
labelID: labelID,
|
|
139
144
|
isGroupLabel: true,
|
|
140
145
|
isOptional: isOptional,
|
|
146
|
+
isRequired: isRequired,
|
|
141
147
|
descriptionID: description ? descriptionID : undefined,
|
|
142
148
|
isDisabled: isDisabled,
|
|
143
149
|
status: status ? {
|
|
@@ -158,7 +164,7 @@ export function CheckboxList({
|
|
|
158
164
|
...rest,
|
|
159
165
|
// The disabled-message tooltip anchors here; its handlers are gated
|
|
160
166
|
// internally by isEnabled, so attaching unconditionally is safe.
|
|
161
|
-
ref: useMergedRefs(ref, disabledMessageTooltip.ref),
|
|
167
|
+
ref: useMergedRefs(ref, disabledMessageTooltip.ref, observeRequiredGroup),
|
|
162
168
|
...mergeProps(stylex.props(controlXstyle), controlClassName, controlStyle),
|
|
163
169
|
role: "group"
|
|
164
170
|
// Screen readers announce the whole group as unavailable. Its dimmed
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RequiredGroup } from './requiredGroup.js';
|
|
1
2
|
export interface CheckboxListContextValue {
|
|
2
3
|
value?: string[];
|
|
3
4
|
/**
|
|
@@ -36,6 +37,12 @@ export interface CheckboxListContextValue {
|
|
|
36
37
|
* when idle.
|
|
37
38
|
*/
|
|
38
39
|
loadingValues?: ReadonlyArray<string>;
|
|
40
|
+
/**
|
|
41
|
+
* Present when the list is `isRequired`. Each item registers its checkbox,
|
|
42
|
+
* so that while no box is checked the first enabled box carries native
|
|
43
|
+
* `required`.
|
|
44
|
+
*/
|
|
45
|
+
requiredGroup?: RequiredGroup;
|
|
39
46
|
}
|
|
40
47
|
export declare const CheckboxListContext: import("react").Context<CheckboxListContextValue | null>;
|
|
41
48
|
//# sourceMappingURL=CheckboxListContext.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CheckboxListContext.d.ts","sourceRoot":"","sources":["../../src/CheckboxList/CheckboxListContext.tsx"],"names":[],"mappings":"AAaA,MAAM,WAAW,wBAAwB;IACvC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7D,UAAU,EAAE,OAAO,CAAC;IACpB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"CheckboxListContext.d.ts","sourceRoot":"","sources":["../../src/CheckboxList/CheckboxListContext.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAEnD,MAAM,WAAW,wBAAwB;IACvC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7D,UAAU,EAAE,OAAO,CAAC;IACpB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACtC;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,eAAO,MAAM,mBAAmB,0DACiC,CAAC"}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file CheckboxListContext.tsx
|
|
7
|
-
* @input Uses the private layer-scoped context factory
|
|
7
|
+
* @input Uses the private layer-scoped context factory and the RequiredGroup
|
|
8
|
+
* type
|
|
8
9
|
* @output Exports CheckboxListContext for parent-child communication
|
|
9
10
|
* @position Internal context; consumed by CheckboxList.tsx and CheckboxListItem.tsx
|
|
10
11
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CheckboxListItem.d.ts","sourceRoot":"","sources":["../../src/CheckboxList/CheckboxListItem.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"CheckboxListItem.d.ts","sourceRoot":"","sources":["../../src/CheckboxList/CheckboxListItem.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAkC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAItE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAU5C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,SAAS,CAAC;AAuBvC,MAAM,WAAW,qBAAsB,SAAQ,SAAS,CAAC,aAAa,CAAC;IACrE;;;;;OAKG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;;;;;;;;;;;OAaG;IACH,KAAK,EAAE,SAAS,CAAC;IACjB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB;;OAEG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,eAAe,CAAC;IACtC;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACrC,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;CAChC;AAyCD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,YAAY,EAAE,SAAS,EACvB,KAAK,EACL,WAAW,EACX,UAAU,EACV,UAAU,EAAE,cAAsB,EAClC,SAAS,EAAE,aAAqB,EAChC,SAAS,EACT,OAAO,EACP,GAAG,EACH,MAAM,EACN,SAAS,EACT,KAAK,EACL,OAAO,EAAE,WAAW,EACpB,KAAK,EACL,GAAG,SAAS,EACb,EAAE,qBAAqB,+BAiKvB;yBAlLe,gBAAgB"}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* - /apps/storybook/stories/CheckboxList.stories.tsx
|
|
17
17
|
* - /packages/cli/assets/templates/blocks/components/CheckboxList/ (showcase blocks)
|
|
18
18
|
*/
|
|
19
|
-
import { use, useId, useRef } from 'react';
|
|
19
|
+
import { use, useCallback, useId, useRef } from 'react';
|
|
20
20
|
import * as stylex from '@stylexjs/stylex';
|
|
21
21
|
import "../theme/tokens.stylex.js";
|
|
22
22
|
import { colorVars } from "../theme/tokens.stylex.js";
|
|
@@ -26,6 +26,7 @@ import { ListContext } from "../List/ListContext.js";
|
|
|
26
26
|
import { CheckboxListContext } from "./CheckboxListContext.js";
|
|
27
27
|
import { ItemDescriptionContext } from "../Item/ItemDescriptionContext.js";
|
|
28
28
|
import { joinAriaIDs } from "../utils/inputAria.js";
|
|
29
|
+
import { useMergedRefs } from "../hooks/useMergedRefs.js";
|
|
29
30
|
import { registerEnglishFallback, useComponentTranslator } from "../i18n/useComponentTranslator.js";
|
|
30
31
|
import english from "../i18n/generated-english/CheckboxList/CheckboxListItem.generated.js";
|
|
31
32
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
@@ -172,6 +173,17 @@ export function CheckboxListItem({
|
|
|
172
173
|
const checkboxRef = useRef(null);
|
|
173
174
|
const hasRowInteraction = isInteractive || onClickProp != null;
|
|
174
175
|
|
|
176
|
+
// A required group decides which box carries native `required` from the
|
|
177
|
+
// state of every box. A change to this box's state registers it again.
|
|
178
|
+
const requiredGroup = ctx?.requiredGroup;
|
|
179
|
+
const isBoxChecked = resolvedChecked === true;
|
|
180
|
+
const isBoxEnabled = !effectiveDisabled && !effectiveReadOnly;
|
|
181
|
+
const registerWithRequiredGroup = useCallback(input => input != null && requiredGroup != null ? requiredGroup.register(input, {
|
|
182
|
+
isChecked: isBoxChecked,
|
|
183
|
+
isEnabled: isBoxEnabled
|
|
184
|
+
}) : undefined, [requiredGroup, isBoxChecked, isBoxEnabled]);
|
|
185
|
+
const checkboxInputRef = useMergedRefs(checkboxRef, registerWithRequiredGroup);
|
|
186
|
+
|
|
175
187
|
// A read-only checkbox carries aria-readonly, which the row's clickable
|
|
176
188
|
// container treats as a non-interactive target, so a click on it would be
|
|
177
189
|
// delegated straight back to the checkbox without end. Stop that click at
|
|
@@ -222,7 +234,7 @@ export function CheckboxListItem({
|
|
|
222
234
|
className: className,
|
|
223
235
|
style: style,
|
|
224
236
|
startContent: /*#__PURE__*/_jsx(DescribedCheckboxInput, {
|
|
225
|
-
ref:
|
|
237
|
+
ref: checkboxInputRef,
|
|
226
238
|
label: checkboxLabel,
|
|
227
239
|
"aria-labelledby": namesFromVisibleLabel ? labelID : undefined,
|
|
228
240
|
isLabelHidden: true,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** The state of one box that decides which box carries `required`. */
|
|
2
|
+
export interface RequiredGroupBox {
|
|
3
|
+
isChecked: boolean;
|
|
4
|
+
/** False for a disabled or read-only box, which the user cannot check. */
|
|
5
|
+
isEnabled: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface RequiredGroup {
|
|
8
|
+
/**
|
|
9
|
+
* Adds a checkbox with its state and updates `required` on every box.
|
|
10
|
+
* Returns a function that removes the checkbox, clears its `required`, and
|
|
11
|
+
* updates the rest. An item registers again when its state changes.
|
|
12
|
+
*/
|
|
13
|
+
register: (input: HTMLInputElement, box: RequiredGroupBox) => () => void;
|
|
14
|
+
/**
|
|
15
|
+
* Updates `required` when the group's options move, for example after a
|
|
16
|
+
* keyed reorder, which registers no box again. Returns a function that
|
|
17
|
+
* stops it.
|
|
18
|
+
*/
|
|
19
|
+
observe: (container: HTMLElement) => () => void;
|
|
20
|
+
}
|
|
21
|
+
export declare function createRequiredGroup(): RequiredGroup;
|
|
22
|
+
//# sourceMappingURL=requiredGroup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requiredGroup.d.ts","sourceRoot":"","sources":["../../src/CheckboxList/requiredGroup.ts"],"names":[],"mappings":"AAkBA,sEAAsE;AACtE,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,0EAA0E;IAC1E,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,gBAAgB,EAAE,GAAG,EAAE,gBAAgB,KAAK,MAAM,IAAI,CAAC;IACzE;;;;OAIG;IACH,OAAO,EAAE,CAAC,SAAS,EAAE,WAAW,KAAK,MAAM,IAAI,CAAC;CACjD;AAUD,wBAAgB,mBAAmB,IAAI,aAAa,CA4BnD"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file requiredGroup.ts
|
|
5
|
+
* @input The checkbox inputs of a required CheckboxList and each box's state;
|
|
6
|
+
* Core's shared mutation observer
|
|
7
|
+
* @output Exports createRequiredGroup, RequiredGroup, and RequiredGroupBox
|
|
8
|
+
* @position Internal to CheckboxList; CheckboxList creates one for an
|
|
9
|
+
* `isRequired` group and CheckboxListItem registers its checkbox with it
|
|
10
|
+
*
|
|
11
|
+
* The browser has no required state for a group of checkboxes. While no box
|
|
12
|
+
* is checked, the first enabled box carries native `required`. The browser
|
|
13
|
+
* then blocks submit and moves focus to that box with its own message. Once
|
|
14
|
+
* any box is checked, no box carries `required`.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { observeMutations } from "../utils/sharedMutationObserver.js";
|
|
18
|
+
|
|
19
|
+
/** The state of one box that decides which box carries `required`. */
|
|
20
|
+
|
|
21
|
+
const MOVE_OPTIONS = {
|
|
22
|
+
childList: true,
|
|
23
|
+
subtree: true
|
|
24
|
+
};
|
|
25
|
+
function byDocumentOrder(a, b) {
|
|
26
|
+
return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
|
|
27
|
+
}
|
|
28
|
+
export function createRequiredGroup() {
|
|
29
|
+
const boxes = new Map();
|
|
30
|
+
function update() {
|
|
31
|
+
const ordered = [...boxes.keys()].sort(byDocumentOrder);
|
|
32
|
+
const isEmpty = ![...boxes.values()].some(box => box.isChecked);
|
|
33
|
+
const target = isEmpty ? ordered.find(input => boxes.get(input)?.isEnabled === true) : undefined;
|
|
34
|
+
for (const input of ordered) {
|
|
35
|
+
input.required = input === target;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
register(input, box) {
|
|
40
|
+
boxes.set(input, box);
|
|
41
|
+
update();
|
|
42
|
+
return () => {
|
|
43
|
+
boxes.delete(input);
|
|
44
|
+
input.required = false;
|
|
45
|
+
update();
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
observe(container) {
|
|
49
|
+
return observeMutations(container, MOVE_OPTIONS, update);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* @output Exports observeMutations (one shared observer per set of options)
|
|
5
5
|
* and observeContent, its text-and-children form
|
|
6
6
|
* @position Internal utility; consumed by useTruncation, hitArea, AppShell's
|
|
7
|
-
* main-overflow observer
|
|
7
|
+
* main-overflow observer, ModalAnnounceProvider's presentation watch and
|
|
8
|
+
* CheckboxList's required group. The mutation counterpart of sharedResizeObserver:
|
|
8
9
|
* one observer for every element observed with the same options, so a
|
|
9
10
|
* repeated cell, row or region does not create one observer per instance.
|
|
10
11
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sharedMutationObserver.d.ts","sourceRoot":"","sources":["../../src/utils/sharedMutationObserver.ts"],"names":[],"mappings":"AAIA
|
|
1
|
+
{"version":3,"file":"sharedMutationObserver.d.ts","sourceRoot":"","sources":["../../src/utils/sharedMutationObserver.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH,KAAK,gBAAgB,GAAG,MAAM,IAAI,CAAC;AA4DnC;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,IAAI,EACb,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,gBAAgB,GACzB,MAAM,IAAI,CAmCZ;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,gBAAgB,GACzB,MAAM,IAAI,CAEZ"}
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* @output Exports observeMutations (one shared observer per set of options)
|
|
9
9
|
* and observeContent, its text-and-children form
|
|
10
10
|
* @position Internal utility; consumed by useTruncation, hitArea, AppShell's
|
|
11
|
-
* main-overflow observer
|
|
11
|
+
* main-overflow observer, ModalAnnounceProvider's presentation watch and
|
|
12
|
+
* CheckboxList's required group. The mutation counterpart of sharedResizeObserver:
|
|
12
13
|
* one observer for every element observed with the same options, so a
|
|
13
14
|
* repeated cell, row or region does not create one observer per instance.
|
|
14
15
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/core",
|
|
3
|
-
"version": "0.7.8-canary.
|
|
3
|
+
"version": "0.7.8-canary.e4c81f3",
|
|
4
4
|
"displayName": "Astryx Core",
|
|
5
5
|
"description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
|
|
6
6
|
"author": "Meta Open Source",
|
|
@@ -870,9 +870,9 @@
|
|
|
870
870
|
"react-dom": ">=19.0.0"
|
|
871
871
|
},
|
|
872
872
|
"devDependencies": {
|
|
873
|
-
"@astryxdesign/a11y-spec": "0.7.8-canary.
|
|
874
|
-
"@astryxdesign/cli": "0.7.8-canary.
|
|
875
|
-
"@astryxdesign/interaction-harness": "0.7.8-canary.
|
|
873
|
+
"@astryxdesign/a11y-spec": "0.7.8-canary.e4c81f3",
|
|
874
|
+
"@astryxdesign/cli": "0.7.8-canary.e4c81f3",
|
|
875
|
+
"@astryxdesign/interaction-harness": "0.7.8-canary.e4c81f3",
|
|
876
876
|
"@babel/cli": "^7.29.7",
|
|
877
877
|
"@babel/core": "^7.29.7",
|
|
878
878
|
"@babel/preset-react": "^7.29.7",
|
|
@@ -134,6 +134,13 @@ export const docs = {
|
|
|
134
134
|
'Whether the group is optional. Shows the optional indicator after the group label, unless a surrounding FormLayout already makes fields optional by default.',
|
|
135
135
|
default: 'false',
|
|
136
136
|
},
|
|
137
|
+
{
|
|
138
|
+
name: 'isRequired',
|
|
139
|
+
type: 'boolean',
|
|
140
|
+
description:
|
|
141
|
+
'Whether the group needs at least one checked box. Shows the required indicator after the group label by the same rules as every input field. While no box is checked, the browser blocks submit and moves focus to the first box that is neither disabled nor read-only, with its own message. A disabled or read-only group requires nothing. Show your own error text through status. Mutually exclusive with isOptional.',
|
|
142
|
+
default: 'false',
|
|
143
|
+
},
|
|
137
144
|
{
|
|
138
145
|
name: 'density',
|
|
139
146
|
type: "'compact' | 'balanced' | 'spacious'",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file CheckboxList.tsx
|
|
7
|
-
* @input Uses React, useId, useOptimistic, useTransition, Field, List, CheckboxListContext
|
|
7
|
+
* @input Uses React, useId, useOptimistic, useTransition, Field, List, CheckboxListContext, requiredGroup
|
|
8
8
|
* @output Exports CheckboxList component, CheckboxListProps
|
|
9
9
|
* @position Core implementation; consumed by index.ts, tested by CheckboxList.test.tsx
|
|
10
10
|
*
|
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
CheckboxListContext,
|
|
44
44
|
type CheckboxListContextValue,
|
|
45
45
|
} from './CheckboxListContext';
|
|
46
|
+
import {createRequiredGroup} from './requiredGroup';
|
|
46
47
|
|
|
47
48
|
const EMPTY_ARRAY: string[] = [];
|
|
48
49
|
|
|
@@ -72,6 +73,17 @@ export interface CheckboxListProps extends Omit<
|
|
|
72
73
|
* @default false
|
|
73
74
|
*/
|
|
74
75
|
isOptional?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Whether the group needs at least one checked box. Shows the required
|
|
78
|
+
* indicator after the group label by the same rules as every input field.
|
|
79
|
+
* While no box is checked, the browser blocks submit and moves focus to
|
|
80
|
+
* the first box that is neither disabled nor read-only. A disabled or
|
|
81
|
+
* read-only group requires nothing. Show your own error text through
|
|
82
|
+
* `status`.
|
|
83
|
+
* Mutually exclusive with isOptional.
|
|
84
|
+
* @default false
|
|
85
|
+
*/
|
|
86
|
+
isRequired?: boolean;
|
|
75
87
|
/**
|
|
76
88
|
* Status indicator for the checkbox group.
|
|
77
89
|
* When set with a message, displays a colored message box below the group.
|
|
@@ -161,6 +173,7 @@ export function CheckboxList({
|
|
|
161
173
|
isLabelHidden = false,
|
|
162
174
|
description,
|
|
163
175
|
isOptional = false,
|
|
176
|
+
isRequired = false,
|
|
164
177
|
status,
|
|
165
178
|
value,
|
|
166
179
|
onChange,
|
|
@@ -193,6 +206,18 @@ export function CheckboxList({
|
|
|
193
206
|
const descriptionID = useId();
|
|
194
207
|
const statusMessageID = useId();
|
|
195
208
|
|
|
209
|
+
const requiredGroup = useMemo(
|
|
210
|
+
() => (isRequired ? createRequiredGroup() : undefined),
|
|
211
|
+
[isRequired],
|
|
212
|
+
);
|
|
213
|
+
const observeRequiredGroup = useCallback(
|
|
214
|
+
(container: HTMLDivElement | null) =>
|
|
215
|
+
container != null && requiredGroup != null
|
|
216
|
+
? requiredGroup.observe(container)
|
|
217
|
+
: undefined,
|
|
218
|
+
[requiredGroup],
|
|
219
|
+
);
|
|
220
|
+
|
|
196
221
|
const [, startTransition] = useTransition();
|
|
197
222
|
const isCollectionMode = value !== undefined;
|
|
198
223
|
const effectiveValue = value ?? EMPTY_ARRAY;
|
|
@@ -256,6 +281,7 @@ export function CheckboxList({
|
|
|
256
281
|
isReadOnly,
|
|
257
282
|
isInvalid: status?.type === 'error',
|
|
258
283
|
loadingValues,
|
|
284
|
+
requiredGroup,
|
|
259
285
|
}),
|
|
260
286
|
[
|
|
261
287
|
isCollectionMode,
|
|
@@ -267,6 +293,7 @@ export function CheckboxList({
|
|
|
267
293
|
isReadOnly,
|
|
268
294
|
status?.type,
|
|
269
295
|
loadingValues,
|
|
296
|
+
requiredGroup,
|
|
270
297
|
],
|
|
271
298
|
);
|
|
272
299
|
|
|
@@ -279,6 +306,7 @@ export function CheckboxList({
|
|
|
279
306
|
labelID={labelID}
|
|
280
307
|
isGroupLabel
|
|
281
308
|
isOptional={isOptional}
|
|
309
|
+
isRequired={isRequired}
|
|
282
310
|
descriptionID={description ? descriptionID : undefined}
|
|
283
311
|
isDisabled={isDisabled}
|
|
284
312
|
status={
|
|
@@ -302,7 +330,11 @@ export function CheckboxList({
|
|
|
302
330
|
{...rest}
|
|
303
331
|
// The disabled-message tooltip anchors here; its handlers are gated
|
|
304
332
|
// internally by isEnabled, so attaching unconditionally is safe.
|
|
305
|
-
ref={useMergedRefs(
|
|
333
|
+
ref={useMergedRefs(
|
|
334
|
+
ref,
|
|
335
|
+
disabledMessageTooltip.ref,
|
|
336
|
+
observeRequiredGroup,
|
|
337
|
+
)}
|
|
306
338
|
{...mergeProps(
|
|
307
339
|
stylex.props(controlXstyle),
|
|
308
340
|
controlClassName,
|
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file CheckboxListContext.tsx
|
|
7
|
-
* @input Uses the private layer-scoped context factory
|
|
7
|
+
* @input Uses the private layer-scoped context factory and the RequiredGroup
|
|
8
|
+
* type
|
|
8
9
|
* @output Exports CheckboxListContext for parent-child communication
|
|
9
10
|
* @position Internal context; consumed by CheckboxList.tsx and CheckboxListItem.tsx
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
import {createLayerScopedContext} from '../Layer/layerScopedContext';
|
|
14
|
+
import type {RequiredGroup} from './requiredGroup';
|
|
13
15
|
|
|
14
16
|
export interface CheckboxListContextValue {
|
|
15
17
|
value?: string[];
|
|
@@ -49,6 +51,12 @@ export interface CheckboxListContextValue {
|
|
|
49
51
|
* when idle.
|
|
50
52
|
*/
|
|
51
53
|
loadingValues?: ReadonlyArray<string>;
|
|
54
|
+
/**
|
|
55
|
+
* Present when the list is `isRequired`. Each item registers its checkbox,
|
|
56
|
+
* so that while no box is checked the first enabled box carries native
|
|
57
|
+
* `required`.
|
|
58
|
+
*/
|
|
59
|
+
requiredGroup?: RequiredGroup;
|
|
52
60
|
}
|
|
53
61
|
|
|
54
62
|
export const CheckboxListContext =
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* - /packages/cli/assets/templates/blocks/components/CheckboxList/ (showcase blocks)
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import {use, useId, useRef, type ReactNode} from 'react';
|
|
20
|
+
import {use, useCallback, useId, useRef, type ReactNode} from 'react';
|
|
21
21
|
import * as stylex from '@stylexjs/stylex';
|
|
22
22
|
import type {StyleXStyles} from '@stylexjs/stylex';
|
|
23
23
|
import {colorVars} from '../theme/tokens.stylex';
|
|
@@ -30,6 +30,7 @@ import {ListContext} from '../List/ListContext';
|
|
|
30
30
|
import {CheckboxListContext} from './CheckboxListContext';
|
|
31
31
|
import {ItemDescriptionContext} from '../Item/ItemDescriptionContext';
|
|
32
32
|
import {joinAriaIDs} from '../utils/inputAria';
|
|
33
|
+
import {useMergedRefs} from '../hooks/useMergedRefs';
|
|
33
34
|
import type {ItemAlign} from '../Item';
|
|
34
35
|
import {
|
|
35
36
|
registerEnglishFallback,
|
|
@@ -284,6 +285,26 @@ export function CheckboxListItem({
|
|
|
284
285
|
const checkboxRef = useRef<HTMLInputElement | null>(null);
|
|
285
286
|
const hasRowInteraction = isInteractive || onClickProp != null;
|
|
286
287
|
|
|
288
|
+
// A required group decides which box carries native `required` from the
|
|
289
|
+
// state of every box. A change to this box's state registers it again.
|
|
290
|
+
const requiredGroup = ctx?.requiredGroup;
|
|
291
|
+
const isBoxChecked = resolvedChecked === true;
|
|
292
|
+
const isBoxEnabled = !effectiveDisabled && !effectiveReadOnly;
|
|
293
|
+
const registerWithRequiredGroup = useCallback(
|
|
294
|
+
(input: HTMLInputElement | null) =>
|
|
295
|
+
input != null && requiredGroup != null
|
|
296
|
+
? requiredGroup.register(input, {
|
|
297
|
+
isChecked: isBoxChecked,
|
|
298
|
+
isEnabled: isBoxEnabled,
|
|
299
|
+
})
|
|
300
|
+
: undefined,
|
|
301
|
+
[requiredGroup, isBoxChecked, isBoxEnabled],
|
|
302
|
+
);
|
|
303
|
+
const checkboxInputRef = useMergedRefs(
|
|
304
|
+
checkboxRef,
|
|
305
|
+
registerWithRequiredGroup,
|
|
306
|
+
);
|
|
307
|
+
|
|
287
308
|
// A read-only checkbox carries aria-readonly, which the row's clickable
|
|
288
309
|
// container treats as a non-interactive target, so a click on it would be
|
|
289
310
|
// delegated straight back to the checkbox without end. Stop that click at
|
|
@@ -349,7 +370,7 @@ export function CheckboxListItem({
|
|
|
349
370
|
style={style}
|
|
350
371
|
startContent={
|
|
351
372
|
<DescribedCheckboxInput
|
|
352
|
-
ref={
|
|
373
|
+
ref={checkboxInputRef}
|
|
353
374
|
label={checkboxLabel}
|
|
354
375
|
aria-labelledby={namesFromVisibleLabel ? labelID : undefined}
|
|
355
376
|
isLabelHidden
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file requiredGroup.ts
|
|
5
|
+
* @input The checkbox inputs of a required CheckboxList and each box's state;
|
|
6
|
+
* Core's shared mutation observer
|
|
7
|
+
* @output Exports createRequiredGroup, RequiredGroup, and RequiredGroupBox
|
|
8
|
+
* @position Internal to CheckboxList; CheckboxList creates one for an
|
|
9
|
+
* `isRequired` group and CheckboxListItem registers its checkbox with it
|
|
10
|
+
*
|
|
11
|
+
* The browser has no required state for a group of checkboxes. While no box
|
|
12
|
+
* is checked, the first enabled box carries native `required`. The browser
|
|
13
|
+
* then blocks submit and moves focus to that box with its own message. Once
|
|
14
|
+
* any box is checked, no box carries `required`.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {observeMutations} from '../utils/sharedMutationObserver';
|
|
18
|
+
|
|
19
|
+
/** The state of one box that decides which box carries `required`. */
|
|
20
|
+
export interface RequiredGroupBox {
|
|
21
|
+
isChecked: boolean;
|
|
22
|
+
/** False for a disabled or read-only box, which the user cannot check. */
|
|
23
|
+
isEnabled: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface RequiredGroup {
|
|
27
|
+
/**
|
|
28
|
+
* Adds a checkbox with its state and updates `required` on every box.
|
|
29
|
+
* Returns a function that removes the checkbox, clears its `required`, and
|
|
30
|
+
* updates the rest. An item registers again when its state changes.
|
|
31
|
+
*/
|
|
32
|
+
register: (input: HTMLInputElement, box: RequiredGroupBox) => () => void;
|
|
33
|
+
/**
|
|
34
|
+
* Updates `required` when the group's options move, for example after a
|
|
35
|
+
* keyed reorder, which registers no box again. Returns a function that
|
|
36
|
+
* stops it.
|
|
37
|
+
*/
|
|
38
|
+
observe: (container: HTMLElement) => () => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const MOVE_OPTIONS: MutationObserverInit = {childList: true, subtree: true};
|
|
42
|
+
|
|
43
|
+
function byDocumentOrder(a: HTMLInputElement, b: HTMLInputElement): number {
|
|
44
|
+
return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING
|
|
45
|
+
? -1
|
|
46
|
+
: 1;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function createRequiredGroup(): RequiredGroup {
|
|
50
|
+
const boxes = new Map<HTMLInputElement, RequiredGroupBox>();
|
|
51
|
+
|
|
52
|
+
function update() {
|
|
53
|
+
const ordered = [...boxes.keys()].sort(byDocumentOrder);
|
|
54
|
+
const isEmpty = ![...boxes.values()].some(box => box.isChecked);
|
|
55
|
+
const target = isEmpty
|
|
56
|
+
? ordered.find(input => boxes.get(input)?.isEnabled === true)
|
|
57
|
+
: undefined;
|
|
58
|
+
for (const input of ordered) {
|
|
59
|
+
input.required = input === target;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
register(input, box) {
|
|
65
|
+
boxes.set(input, box);
|
|
66
|
+
update();
|
|
67
|
+
return () => {
|
|
68
|
+
boxes.delete(input);
|
|
69
|
+
input.required = false;
|
|
70
|
+
update();
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
observe(container) {
|
|
74
|
+
return observeMutations(container, MOVE_OPTIONS, update);
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* @output Exports observeMutations (one shared observer per set of options)
|
|
9
9
|
* and observeContent, its text-and-children form
|
|
10
10
|
* @position Internal utility; consumed by useTruncation, hitArea, AppShell's
|
|
11
|
-
* main-overflow observer
|
|
11
|
+
* main-overflow observer, ModalAnnounceProvider's presentation watch and
|
|
12
|
+
* CheckboxList's required group. The mutation counterpart of sharedResizeObserver:
|
|
12
13
|
* one observer for every element observed with the same options, so a
|
|
13
14
|
* repeated cell, row or region does not create one observer per instance.
|
|
14
15
|
*/
|