@astryxdesign/core 0.7.9-canary.9bd11b3 → 0.7.9-canary.cb2a34a
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/AppShell/observeMainOverflow.d.ts.map +1 -1
- package/dist/AppShell/observeMainOverflow.js +9 -6
- package/dist/InputGroup/InputGroup.d.ts +4 -3
- package/dist/InputGroup/InputGroup.d.ts.map +1 -1
- package/dist/InputGroup/InputGroup.js +2 -2
- package/dist/InputGroup/groupStyles.d.ts +1 -1
- package/dist/InputGroup/groupStyles.d.ts.map +1 -1
- package/dist/InputGroup/groupStyles.js +1 -1
- package/dist/Layout/LayoutContent.d.ts +3 -1
- package/dist/Layout/LayoutContent.d.ts.map +1 -1
- package/dist/Layout/LayoutContent.js +12 -4
- package/dist/Layout/LayoutPanel.d.ts +4 -1
- package/dist/Layout/LayoutPanel.d.ts.map +1 -1
- package/dist/Layout/LayoutPanel.js +13 -4
- package/dist/Stack/StackItem.d.ts +8 -4
- package/dist/Stack/StackItem.d.ts.map +1 -1
- package/dist/Stack/StackItem.js +17 -4
- package/dist/astryx.css +1 -1
- package/dist/hooks/StackItemScrollRegion.d.ts +31 -0
- package/dist/hooks/StackItemScrollRegion.d.ts.map +1 -0
- package/dist/hooks/StackItemScrollRegion.js +40 -0
- package/dist/hooks/useScrollRegionProps.d.ts +35 -0
- package/dist/hooks/useScrollRegionProps.d.ts.map +1 -0
- package/dist/hooks/useScrollRegionProps.js +51 -0
- package/dist/i18n/generated-english/hooks/useScrollRegionProps.generated.d.ts +8 -0
- package/dist/i18n/generated-english/hooks/useScrollRegionProps.generated.d.ts.map +1 -0
- package/dist/i18n/generated-english/hooks/useScrollRegionProps.generated.js +12 -0
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +1 -0
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +1 -0
- package/locales/en.json +4 -0
- package/locales/pseudo.json +3 -0
- package/package.json +4 -4
- package/src/AppShell/observeMainOverflow.ts +9 -6
- package/src/InputGroup/InputGroup.doc.mjs +4 -3
- package/src/InputGroup/InputGroup.tsx +4 -3
- package/src/InputGroup/groupStyles.ts +3 -1
- package/src/Layout/LayoutContent.doc.mjs +6 -3
- package/src/Layout/LayoutContent.tsx +10 -4
- package/src/Layout/LayoutPanel.doc.mjs +6 -3
- package/src/Layout/LayoutPanel.tsx +11 -4
- package/src/Stack/Stack.doc.mjs +1 -1
- package/src/Stack/StackItem.tsx +27 -13
- package/src/hooks/StackItemScrollRegion.tsx +57 -0
- package/src/hooks/useScrollRegionProps.ts +76 -0
- package/src/i18n/generated-english/hooks/useScrollRegionProps.generated.ts +13 -0
- package/src/i18n/generated-locales/en.generated.ts +1 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observeMainOverflow.d.ts","sourceRoot":"","sources":["../../src/AppShell/observeMainOverflow.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"observeMainOverflow.d.ts","sourceRoot":"","sources":["../../src/AppShell/observeMainOverflow.ts"],"names":[],"mappings":"AA4CA;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,WAAW,EACjB,QAAQ,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,GACrC,MAAM,IAAI,CA2FZ"}
|
|
@@ -4,20 +4,23 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file observeMainOverflow.ts
|
|
7
|
-
* @input
|
|
8
|
-
*
|
|
7
|
+
* @input A scroll container that holds its content as direct children with
|
|
8
|
+
* no wrapper: AppShell's main region under `height="fill"`, or a scrolling
|
|
9
|
+
* LayoutContent, LayoutPanel or StackItem
|
|
9
10
|
* @output observeMainOverflow(main, onChange): reports whether main's content
|
|
10
11
|
* overflows it on either axis, once at the start and on every change, and
|
|
11
12
|
* returns the function that stops observing
|
|
12
|
-
* @position Internal
|
|
13
|
+
* @position Internal and never exported. AppShell makes main a
|
|
13
14
|
* keyboard stop only while it overflows (`spec:AST-025` FR12,
|
|
14
|
-
* `component:AppShell` FR4 and PR1).
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* `component:AppShell` FR4 and PR1). useScrollRegionProps names a
|
|
16
|
+
* scrolling region only while it overflows. Main has no inner content
|
|
17
|
+
* box, so its content grows inside it without resizing it: its direct
|
|
18
|
+
* children's sizes are watched instead.
|
|
17
19
|
*
|
|
18
20
|
* SYNC: When modified, update:
|
|
19
21
|
* - /packages/core/src/AppShell/AppShell.tsx
|
|
20
22
|
* - /packages/core/src/AppShell/observeMainOverflow.test.ts
|
|
23
|
+
* - /packages/core/src/hooks/useScrollRegionProps.ts
|
|
21
24
|
*/
|
|
22
25
|
import { getLogicalAxisMapping, measureLogicalScrollAxis } from "../hooks/scrollGeometry.js";
|
|
23
26
|
import { observeMutations } from "../utils/sharedMutationObserver.js";
|
|
@@ -59,7 +59,8 @@ export interface InputGroupProps extends Omit<BaseProps<HTMLDivElement>, 'childr
|
|
|
59
59
|
*/
|
|
60
60
|
size?: InputGroupSize;
|
|
61
61
|
/**
|
|
62
|
-
*
|
|
62
|
+
* Validation status. Its message shows below the group. The group border
|
|
63
|
+
* does not change; a theme can style the row from `data-status`.
|
|
63
64
|
*/
|
|
64
65
|
status?: InputStatus;
|
|
65
66
|
/**
|
|
@@ -72,8 +73,8 @@ export interface InputGroupProps extends Omit<BaseProps<HTMLDivElement>, 'childr
|
|
|
72
73
|
'data-testid'?: string;
|
|
73
74
|
}
|
|
74
75
|
/**
|
|
75
|
-
* Groups an input with prefix
|
|
76
|
-
*
|
|
76
|
+
* Groups an input with prefix and suffix addons in one connected row. A
|
|
77
|
+
* focused member stays above its neighbours, so its focus ring shows in full.
|
|
77
78
|
*
|
|
78
79
|
* @example
|
|
79
80
|
* ```
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InputGroup.d.ts","sourceRoot":"","sources":["../../src/InputGroup/InputGroup.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAsB,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAG1D,OAAO,EAAQ,KAAK,WAAW,EAAC,MAAM,UAAU,CAAC;AAGjD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAK5C,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AA0BhD,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAC3C,SAAS,CAAC,cAAc,CAAC,EACzB,UAAU,CACX;IACC,mDAAmD;IACnD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAEhC;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,IAAI,CAAC,EAAE,cAAc,CAAC;IAEtB
|
|
1
|
+
{"version":3,"file":"InputGroup.d.ts","sourceRoot":"","sources":["../../src/InputGroup/InputGroup.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAsB,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAG1D,OAAO,EAAQ,KAAK,WAAW,EAAC,MAAM,UAAU,CAAC;AAGjD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAK5C,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AA0BhD,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAC3C,SAAS,CAAC,cAAc,CAAC,EACzB,UAAU,CACX;IACC,mDAAmD;IACnD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAEhC;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,IAAI,CAAC,EAAE,cAAc,CAAC;IAEtB;;;OAGG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IAErB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,KAAK,EACL,aAAqB,EACrB,WAAW,EACX,UAAU,EAAE,cAAsB,EAClC,UAAkB,EAClB,UAAkB,EAClB,IAAI,EAAE,QAAQ,EACd,MAAM,EACN,YAAY,EACZ,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,aAAa,EAAE,MAAM,EACrB,GAAG,IAAI,EACR,EAAE,eAAe,+BA8EjB;yBA/Fe,UAAU"}
|
|
@@ -57,8 +57,8 @@ const sizeStyles = {
|
|
|
57
57
|
}
|
|
58
58
|
};
|
|
59
59
|
/**
|
|
60
|
-
* Groups an input with prefix
|
|
61
|
-
*
|
|
60
|
+
* Groups an input with prefix and suffix addons in one connected row. A
|
|
61
|
+
* focused member stays above its neighbours, so its focus ring shows in full.
|
|
62
62
|
*
|
|
63
63
|
* @example
|
|
64
64
|
* ```
|
|
@@ -33,7 +33,7 @@ export declare const groupStyles: Readonly<{
|
|
|
33
33
|
readonly borderStartEndRadius: stylex.StyleXClassNameFor<"borderStartEndRadius", 0 | "8px">;
|
|
34
34
|
readonly borderEndEndRadius: stylex.StyleXClassNameFor<"borderEndEndRadius", 0 | "8px">;
|
|
35
35
|
readonly ':focus-within': stylex.StyleXClassNameFor<":focus-within", {
|
|
36
|
-
readonly zIndex:
|
|
36
|
+
readonly zIndex: 2;
|
|
37
37
|
}>;
|
|
38
38
|
}>;
|
|
39
39
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"groupStyles.d.ts","sourceRoot":"","sources":["../../src/InputGroup/groupStyles.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAG3C;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB;;CAEtB,CAAC;AA0BX,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"groupStyles.d.ts","sourceRoot":"","sources":["../../src/InputGroup/groupStyles.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAG3C;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB;;CAEtB,CAAC;AA0BX,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;EA0DtB,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file LayoutContent.tsx
|
|
3
|
-
* @input Uses React StyleX, LayoutSlotsContext
|
|
3
|
+
* @input Uses React StyleX, LayoutSlotsContext, useScrollRegionProps
|
|
4
4
|
* @output Exports LayoutContent component and LayoutContentProps
|
|
5
5
|
* @position Scrollable main content area for Layout. Wraps the primary body content
|
|
6
6
|
* with automatic scroll containment and context-aware padding.
|
|
@@ -35,6 +35,8 @@ export interface LayoutContentProps extends BaseProps<HTMLDivElement> {
|
|
|
35
35
|
/**
|
|
36
36
|
* Accessible label for the landmark.
|
|
37
37
|
* Required when role is set and multiple landmarks of the same type exist.
|
|
38
|
+
* While the content scrolls, it also names the scrolling region, which
|
|
39
|
+
* otherwise takes a translated "Content".
|
|
38
40
|
*/
|
|
39
41
|
label?: string;
|
|
40
42
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LayoutContent.d.ts","sourceRoot":"","sources":["../../src/Layout/LayoutContent.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,QAAQ,EAAE,SAAS,EAAC,MAAM,OAAO,CAAC;AAC/C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"LayoutContent.d.ts","sourceRoot":"","sources":["../../src/Layout/LayoutContent.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,QAAQ,EAAE,SAAS,EAAC,MAAM,OAAO,CAAC;AAC/C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAQ5C,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,gBAAgB,CAAC;AAqFhD,MAAM,WAAW,kBAAmB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACnE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IAErB;;;;OAIG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;IAEtB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,IAAI,CAAC,EAAE,QAAQ,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,YAAmB,EACnB,OAAO,EACP,KAAK,EACL,IAAI,EACJ,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,KAAK,EACT,EAAE,kBAAkB,+BAoDpB;yBA/De,aAAa"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file LayoutContent.tsx
|
|
7
|
-
* @input Uses React StyleX, LayoutSlotsContext
|
|
7
|
+
* @input Uses React StyleX, LayoutSlotsContext, useScrollRegionProps
|
|
8
8
|
* @output Exports LayoutContent component and LayoutContentProps
|
|
9
9
|
* @position Scrollable main content area for Layout. Wraps the primary body content
|
|
10
10
|
* with automatic scroll containment and context-aware padding.
|
|
@@ -19,6 +19,8 @@ import * as stylex from '@stylexjs/stylex';
|
|
|
19
19
|
import "../theme/tokens.stylex.js";
|
|
20
20
|
import { spacingVars } from "../theme/tokens.stylex.js";
|
|
21
21
|
import { LayoutSlotsContext } from "./LayoutSlotsContext.js";
|
|
22
|
+
import { useMergedRefs } from "../hooks/useMergedRefs.js";
|
|
23
|
+
import { useScrollRegionProps } from "../hooks/useScrollRegionProps.js";
|
|
22
24
|
import { mergeProps } from "../utils/mergeProps.js";
|
|
23
25
|
import { themeProps } from "../utils/themeProps.js";
|
|
24
26
|
import { paddingStyles, containerPaddingInlineVarStyles, containerPaddingBlockStartVarStyles, containerPaddingBlockEndVarStyles } from "./padding.stylex.js";
|
|
@@ -146,10 +148,16 @@ export function LayoutContent({
|
|
|
146
148
|
hasEnd
|
|
147
149
|
} = use(LayoutSlotsContext);
|
|
148
150
|
const isZeroPadding = padding === 0;
|
|
151
|
+
const region = useScrollRegionProps({
|
|
152
|
+
isScrollable,
|
|
153
|
+
role,
|
|
154
|
+
label
|
|
155
|
+
});
|
|
156
|
+
const mergedRef = useMergedRefs(ref, region.ref);
|
|
149
157
|
return /*#__PURE__*/_jsx("div", {
|
|
150
|
-
ref:
|
|
151
|
-
role: role,
|
|
152
|
-
"aria-label": label,
|
|
158
|
+
ref: mergedRef,
|
|
159
|
+
role: region.role,
|
|
160
|
+
"aria-label": region['aria-label'],
|
|
153
161
|
...mergeProps(themeProps('layout-content'), stylex.props(styles.content,
|
|
154
162
|
// Outer padding on container edges (unless content is full bleed)
|
|
155
163
|
!hasStart && !isZeroPadding && padding == null && styles.noStart, !hasEnd && !isZeroPadding && padding == null && styles.noEnd, !hasHeader && !isZeroPadding && padding == null && styles.noHeader, !hasFooter && !isZeroPadding && padding == null && styles.noFooter, isScrollable && styles.scrollable, isZeroPadding && styles.fullBleed, padding != null && paddingStyles[padding], padding != null && containerPaddingInlineVarStyles[padding], padding != null && containerPaddingBlockStartVarStyles[padding], padding != null && containerPaddingBlockEndVarStyles[padding], !hasStart && !hasEnd && !isZeroPadding && styles.constrainedNoPanelsStart, !hasStart && !hasEnd && !isZeroPadding && styles.constrainedNoPanelsEnd, hasStart && !hasEnd && !isZeroPadding && styles.constrainedSingleStartPanel, !hasStart && hasEnd && !isZeroPadding && styles.constrainedSingleEndPanel, xstyle), className, style),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file LayoutPanel.tsx
|
|
3
|
-
* @input Uses React StyleX, LayoutAreaContext, LayoutSlotsContext
|
|
3
|
+
* @input Uses React StyleX, LayoutAreaContext, LayoutSlotsContext,
|
|
4
|
+
* useScrollRegionProps
|
|
4
5
|
* @output Exports LayoutPanel component and LayoutPanelProps
|
|
5
6
|
* @position Sidebar panel for Layout start/end slots. Use for navigation panels,
|
|
6
7
|
* settings sidebars, detail panels, or any fixed-width side content.
|
|
@@ -47,6 +48,8 @@ export interface LayoutPanelProps extends BaseProps<HTMLDivElement> {
|
|
|
47
48
|
/**
|
|
48
49
|
* Accessible label for the landmark.
|
|
49
50
|
* Required when role is set and multiple landmarks of the same type exist.
|
|
51
|
+
* While the panel scrolls, it also names the scrolling region, which
|
|
52
|
+
* otherwise takes a translated "Content".
|
|
50
53
|
*/
|
|
51
54
|
label?: string;
|
|
52
55
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LayoutPanel.d.ts","sourceRoot":"","sources":["../../src/Layout/LayoutPanel.tsx"],"names":[],"mappings":"AAIA
|
|
1
|
+
{"version":3,"file":"LayoutPanel.d.ts","sourceRoot":"","sources":["../../src/Layout/LayoutPanel.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,QAAQ,EAAE,SAAS,EAAC,MAAM,OAAO,CAAC;AAC/C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAS5C,OAAO,KAAK,EAAC,SAAS,EAAE,WAAW,EAAC,MAAM,gBAAgB,CAAC;AAC3D,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAmF9D,MAAM,WAAW,gBAAiB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACjE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IAErB;;;;;;;;;OASG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;;OAIG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;IAEtB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,IAAI,CAAC,EAAE,QAAQ,CAAC;IAEhB;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAElB;;;;;;;;;;;;;OAaG;IACH,SAAS,CAAC,EAAE,cAAc,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,UAAkB,EAClB,YAAmB,EACnB,KAAK,EACL,OAAO,EACP,IAAI,EACJ,KAAK,EACL,SAAS,EACT,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,KAAK,EACT,EAAE,gBAAgB,+BAoElB;yBAlFe,WAAW"}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file LayoutPanel.tsx
|
|
7
|
-
* @input Uses React StyleX, LayoutAreaContext, LayoutSlotsContext
|
|
7
|
+
* @input Uses React StyleX, LayoutAreaContext, LayoutSlotsContext,
|
|
8
|
+
* useScrollRegionProps
|
|
8
9
|
* @output Exports LayoutPanel component and LayoutPanelProps
|
|
9
10
|
* @position Sidebar panel for Layout start/end slots. Use for navigation panels,
|
|
10
11
|
* settings sidebars, detail panels, or any fixed-width side content.
|
|
@@ -20,6 +21,8 @@ import "../theme/tokens.stylex.js";
|
|
|
20
21
|
import { colorVars, spacingVars } from "../theme/tokens.stylex.js";
|
|
21
22
|
import { LayoutAreaContext } from "./LayoutAreaContext.js";
|
|
22
23
|
import { LayoutSlotsContext } from "./LayoutSlotsContext.js";
|
|
24
|
+
import { useMergedRefs } from "../hooks/useMergedRefs.js";
|
|
25
|
+
import { useScrollRegionProps } from "../hooks/useScrollRegionProps.js";
|
|
23
26
|
import { mergeProps } from "../utils/index.js";
|
|
24
27
|
import { themeProps } from "../utils/themeProps.js";
|
|
25
28
|
import { paddingStyles, containerPaddingInlineVarStyles, containerPaddingBlockStartVarStyles, containerPaddingBlockEndVarStyles } from "./padding.stylex.js";
|
|
@@ -160,6 +163,12 @@ export function LayoutPanel({
|
|
|
160
163
|
|
|
161
164
|
// When no divider, collapse spacing for seamless visual flow
|
|
162
165
|
const shouldCollapseSpacing = !hasDivider && !isZeroPadding && padding == null;
|
|
166
|
+
const region = useScrollRegionProps({
|
|
167
|
+
isScrollable,
|
|
168
|
+
role,
|
|
169
|
+
label
|
|
170
|
+
});
|
|
171
|
+
const mergedRef = useMergedRefs(ref, region.ref);
|
|
163
172
|
|
|
164
173
|
// Select divider style based on position
|
|
165
174
|
const dividerStyle = isStartPanel ? styles.dividerEnd : isEndPanel ? styles.dividerStart : null;
|
|
@@ -167,9 +176,9 @@ export function LayoutPanel({
|
|
|
167
176
|
// Select collapse style based on position (collapse the side where divider would be)
|
|
168
177
|
const collapseStyle = isStartPanel ? styles.collapseEnd : isEndPanel ? styles.collapseStart : null;
|
|
169
178
|
return /*#__PURE__*/_jsx("div", {
|
|
170
|
-
ref:
|
|
171
|
-
role: role,
|
|
172
|
-
"aria-label": label,
|
|
179
|
+
ref: mergedRef,
|
|
180
|
+
role: region.role,
|
|
181
|
+
"aria-label": region['aria-label'],
|
|
173
182
|
...mergeProps(themeProps('layout-panel'), stylex.props(styles.panel, dynamicStyles.sizing(effectiveWidth ?? null),
|
|
174
183
|
// Outer padding on container edges (unless component is full bleed)
|
|
175
184
|
isStartPanel && !isZeroPadding && padding == null && styles.startPanel, isEndPanel && !isZeroPadding && padding == null && styles.endPanel, !hasHeader && !isZeroPadding && padding == null && styles.noHeader, !hasFooter && !isZeroPadding && padding == null && styles.noFooter, isScrollable && styles.scrollable, isZeroPadding && styles.fullBleed, padding != null && paddingStyles[padding], padding != null && containerPaddingInlineVarStyles[padding], padding != null && containerPaddingBlockStartVarStyles[padding], padding != null && containerPaddingBlockEndVarStyles[padding], hasDivider && dividerStyle, shouldCollapseSpacing && collapseStyle, xstyle), className, style),
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file StackItem.tsx
|
|
3
|
-
* @input Uses React, ElementType, stackItem utility
|
|
3
|
+
* @input Uses React, ElementType, stackItem utility, StackItemScrollRegion
|
|
4
4
|
* @output Exports StackItem polymorphic component and StackItemProps
|
|
5
|
-
* @position Layout/Stack component; uses stackItem.stylex.ts
|
|
5
|
+
* @position Layout/Stack component; uses stackItem.stylex.ts. A scrollable
|
|
6
|
+
* `div` or `span` item renders through StackItemScrollRegion, which names
|
|
7
|
+
* it while it scrolls.
|
|
6
8
|
*
|
|
7
9
|
* SYNC: When modified, update these files to stay in sync:
|
|
8
10
|
* - /packages/core/src/Stack/Stack.doc.mjs
|
|
@@ -34,7 +36,9 @@ export interface StackItemProps extends BaseProps<HTMLElement> {
|
|
|
34
36
|
* reset, so `<StackItem size="fill" isScrollable>` is a complete scroll
|
|
35
37
|
* region — it grows to fill the stack and scrolls its own overflow with
|
|
36
38
|
* no extra style plumbing. Matches `isScrollable` on `LayoutContent`
|
|
37
|
-
* and `LayoutPanel`.
|
|
39
|
+
* and `LayoutPanel`. While its content overflows, a `div` or `span` item
|
|
40
|
+
* is a group named by the caller's `aria-label`, or by a translated
|
|
41
|
+
* "Content". Another element keeps its own role.
|
|
38
42
|
* @default false
|
|
39
43
|
*/
|
|
40
44
|
isScrollable?: boolean;
|
|
@@ -62,7 +66,7 @@ export interface StackItemProps extends BaseProps<HTMLElement> {
|
|
|
62
66
|
* </HStack>
|
|
63
67
|
* ```
|
|
64
68
|
*/
|
|
65
|
-
export declare function StackItem({ crossAlignSelf, size, isScrollable, as: element, xstyle, className, style, children, ref, ...props }: StackItemProps): import("react").ReactElement<any, string | import("react").JSXElementConstructor<any
|
|
69
|
+
export declare function StackItem({ crossAlignSelf, size, isScrollable, as: element, xstyle, className, style, children, ref, ...props }: StackItemProps): import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | import("react").FunctionComponentElement<import("../hooks/StackItemScrollRegion.js").StackItemScrollRegionProps>;
|
|
66
70
|
export declare namespace StackItem {
|
|
67
71
|
var displayName: string;
|
|
68
72
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StackItem.d.ts","sourceRoot":"","sources":["../../src/Stack/StackItem.tsx"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"StackItem.d.ts","sourceRoot":"","sources":["../../src/Stack/StackItem.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAgB,KAAK,WAAW,EAAE,KAAK,SAAS,EAAW,MAAM,OAAO,CAAC;AAChF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAE5C,OAAO,EAEL,KAAK,uBAAuB,EAC5B,KAAK,aAAa,EACnB,MAAM,oBAAoB,CAAC;AAW5B,MAAM,WAAW,cAAe,SAAQ,SAAS,CAAC,WAAW,CAAC;IAC5D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC7B;;;OAGG;IACH,cAAc,CAAC,EAAE,uBAAuB,CAAC;IAEzC;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,aAAa,CAAC;IAErB;;;;;;;;;;;OAWG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;OAGG;IACH,EAAE,CAAC,EAAE,WAAW,CAAC;IAEjB;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,EACxB,cAAc,EACd,IAAI,EACJ,YAAY,EACZ,EAAE,EAAE,OAAe,EACnB,MAAM,EACN,SAAS,EACT,KAAK,EACL,QAAQ,EACR,GAAG,EACH,GAAG,KAAK,EACT,EAAE,cAAc,0MA8BhB;yBAzCe,SAAS"}
|
package/dist/Stack/StackItem.js
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @file StackItem.tsx
|
|
5
|
-
* @input Uses React, ElementType, stackItem utility
|
|
5
|
+
* @input Uses React, ElementType, stackItem utility, StackItemScrollRegion
|
|
6
6
|
* @output Exports StackItem polymorphic component and StackItemProps
|
|
7
|
-
* @position Layout/Stack component; uses stackItem.stylex.ts
|
|
7
|
+
* @position Layout/Stack component; uses stackItem.stylex.ts. A scrollable
|
|
8
|
+
* `div` or `span` item renders through StackItemScrollRegion, which names
|
|
9
|
+
* it while it scrolls.
|
|
8
10
|
*
|
|
9
11
|
* SYNC: When modified, update these files to stay in sync:
|
|
10
12
|
* - /packages/core/src/Stack/Stack.doc.mjs
|
|
@@ -16,6 +18,7 @@ import * as stylex from '@stylexjs/stylex';
|
|
|
16
18
|
import { stackItem } from "./stackItem.stylex.js";
|
|
17
19
|
import { mergeProps } from "../utils/mergeProps.js";
|
|
18
20
|
import { themeProps } from "../utils/themeProps.js";
|
|
21
|
+
import { StackItemScrollRegion } from "../hooks/StackItemScrollRegion.js";
|
|
19
22
|
const overflowStyles = {
|
|
20
23
|
scrollable: {
|
|
21
24
|
kVQacm: "xysyzu8",
|
|
@@ -52,12 +55,22 @@ export function StackItem({
|
|
|
52
55
|
crossAlignSelf,
|
|
53
56
|
size
|
|
54
57
|
}), isScrollable && overflowStyles.scrollable, xstyle);
|
|
55
|
-
|
|
56
|
-
ref: ref,
|
|
58
|
+
const elementProps = {
|
|
57
59
|
...mergeProps(themeProps('stack-item', {
|
|
58
60
|
size
|
|
59
61
|
}), stylexProps, className, style),
|
|
60
62
|
...props
|
|
63
|
+
};
|
|
64
|
+
if (isScrollable && (element === 'div' || element === 'span')) {
|
|
65
|
+
return /*#__PURE__*/createElement(StackItemScrollRegion, {
|
|
66
|
+
as: element,
|
|
67
|
+
ref,
|
|
68
|
+
elementProps
|
|
69
|
+
}, children);
|
|
70
|
+
}
|
|
71
|
+
return /*#__PURE__*/createElement(element, {
|
|
72
|
+
ref: ref,
|
|
73
|
+
...elementProps
|
|
61
74
|
}, children);
|
|
62
75
|
}
|
|
63
76
|
StackItem.displayName = 'StackItem';
|
package/dist/astryx.css
CHANGED
|
@@ -1633,7 +1633,7 @@
|
|
|
1633
1633
|
.xttggg:is([dir="rtl"] *):not(#\#):not(#\#):not(#\#){transform:translateX(100%)}
|
|
1634
1634
|
.x1nyx83j:is([dir="rtl"] *):not(#\#):not(#\#):not(#\#){transform:translateX(50%)}
|
|
1635
1635
|
.x9cjr8z:popover-open:not(#\#):not(#\#):not(#\#){transform:translateY(0)}
|
|
1636
|
-
.
|
|
1636
|
+
.x1pc0aet:focus-within:not(#\#):not(#\#):not(#\#){z-index:2}
|
|
1637
1637
|
.xf5goe6.xf5goe6:where(.astryx-input-group > [data-astryx-input-group-item]):focus-visible:not(#\#):not(#\#):not(#\#){z-index:2}
|
|
1638
1638
|
.x151j4nw:has(input[aria-disabled="true"]:focus-visible):not(#\#):not(#\#):not(#\#){box-shadow:inset 0 0 0 2px var(--color-accent-muted)}
|
|
1639
1639
|
.xwmge8v:has(> [aria-disabled="true"]:focus-visible):not(#\#):not(#\#):not(#\#){box-shadow:inset 0 0 0 2px var(--color-accent-muted)}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file StackItemScrollRegion.tsx
|
|
3
|
+
* @input A scrollable `div` or `span` StackItem's props, ref and children
|
|
4
|
+
* @output StackItemScrollRegion (internal; not re-exported from
|
|
5
|
+
* hooks/index.ts), which renders that element and names it as a group
|
|
6
|
+
* while its content overflows
|
|
7
|
+
* @position Internal client island for StackItem, which itself stays a
|
|
8
|
+
* static render. Only a scrollable `div` or `span` item measures its
|
|
9
|
+
* overflow, on the client; another element keeps its own role.
|
|
10
|
+
*
|
|
11
|
+
* SYNC: When modified, update:
|
|
12
|
+
* - /packages/core/src/Stack/StackItem.tsx
|
|
13
|
+
* - /packages/core/src/serverSafeComponents.test.ts
|
|
14
|
+
*/
|
|
15
|
+
import { type AriaRole, type ReactNode, type Ref } from 'react';
|
|
16
|
+
export interface StackItemScrollRegionProps {
|
|
17
|
+
as: 'div' | 'span';
|
|
18
|
+
ref?: Ref<HTMLElement>;
|
|
19
|
+
elementProps: {
|
|
20
|
+
role?: AriaRole;
|
|
21
|
+
'aria-label'?: string;
|
|
22
|
+
[prop: string]: unknown;
|
|
23
|
+
};
|
|
24
|
+
children?: ReactNode;
|
|
25
|
+
}
|
|
26
|
+
export declare function StackItemScrollRegion({ as: element, ref, elementProps, children, }: StackItemScrollRegionProps): import("react").DetailedReactHTMLElement<{
|
|
27
|
+
ref: import("react").RefCallback<HTMLElement>;
|
|
28
|
+
role: AriaRole | undefined;
|
|
29
|
+
'aria-label': string | undefined;
|
|
30
|
+
}, HTMLElement>;
|
|
31
|
+
//# sourceMappingURL=StackItemScrollRegion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StackItemScrollRegion.d.ts","sourceRoot":"","sources":["../../src/hooks/StackItemScrollRegion.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAgB,KAAK,QAAQ,EAAE,KAAK,SAAS,EAAE,KAAK,GAAG,EAAC,MAAM,OAAO,CAAC;AAI7E,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,KAAK,GAAG,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC;IACvB,YAAY,EAAE;QACZ,IAAI,CAAC,EAAE,QAAQ,CAAC;QAChB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;KACzB,CAAC;IACF,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,wBAAgB,qBAAqB,CAAC,EACpC,EAAE,EAAE,OAAO,EACX,GAAG,EACH,YAAY,EACZ,QAAQ,GACT,EAAE,0BAA0B;;;;gBAiB5B"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file StackItemScrollRegion.tsx
|
|
7
|
+
* @input A scrollable `div` or `span` StackItem's props, ref and children
|
|
8
|
+
* @output StackItemScrollRegion (internal; not re-exported from
|
|
9
|
+
* hooks/index.ts), which renders that element and names it as a group
|
|
10
|
+
* while its content overflows
|
|
11
|
+
* @position Internal client island for StackItem, which itself stays a
|
|
12
|
+
* static render. Only a scrollable `div` or `span` item measures its
|
|
13
|
+
* overflow, on the client; another element keeps its own role.
|
|
14
|
+
*
|
|
15
|
+
* SYNC: When modified, update:
|
|
16
|
+
* - /packages/core/src/Stack/StackItem.tsx
|
|
17
|
+
* - /packages/core/src/serverSafeComponents.test.ts
|
|
18
|
+
*/
|
|
19
|
+
import { createElement } from 'react';
|
|
20
|
+
import { useMergedRefs } from "./useMergedRefs.js";
|
|
21
|
+
import { useScrollRegionProps } from "./useScrollRegionProps.js";
|
|
22
|
+
export function StackItemScrollRegion({
|
|
23
|
+
as: element,
|
|
24
|
+
ref,
|
|
25
|
+
elementProps,
|
|
26
|
+
children
|
|
27
|
+
}) {
|
|
28
|
+
const region = useScrollRegionProps({
|
|
29
|
+
isScrollable: true,
|
|
30
|
+
role: elementProps.role,
|
|
31
|
+
label: elementProps['aria-label']
|
|
32
|
+
});
|
|
33
|
+
const mergedRef = useMergedRefs(ref, region.ref);
|
|
34
|
+
return /*#__PURE__*/createElement(element, {
|
|
35
|
+
...elementProps,
|
|
36
|
+
ref: mergedRef,
|
|
37
|
+
role: region.role,
|
|
38
|
+
'aria-label': region['aria-label']
|
|
39
|
+
}, children);
|
|
40
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file useScrollRegionProps.ts
|
|
3
|
+
* @input Whether a region may scroll, and the caller's role and label
|
|
4
|
+
* @output useScrollRegionProps: a ref for the region, and the role and
|
|
5
|
+
* accessible name that the region carries while its content overflows
|
|
6
|
+
* @position Internal hook for LayoutContent, LayoutPanel and StackItem.
|
|
7
|
+
*
|
|
8
|
+
* SYNC: When modified, update:
|
|
9
|
+
* - /packages/core/src/hooks/useScrollRegionProps.test.tsx
|
|
10
|
+
* - /packages/core/src/AppShell/observeMainOverflow.ts
|
|
11
|
+
*/
|
|
12
|
+
import { type AriaRole, type RefCallback } from 'react';
|
|
13
|
+
export interface ScrollRegionOptions {
|
|
14
|
+
/** Whether the region may scroll at all (`overflow: auto`). */
|
|
15
|
+
isScrollable: boolean;
|
|
16
|
+
/** The caller's role. It always wins, and takes no default name. */
|
|
17
|
+
role?: AriaRole;
|
|
18
|
+
/** The caller's accessible name. It always wins. */
|
|
19
|
+
label?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface ScrollRegionProps {
|
|
22
|
+
ref: RefCallback<HTMLElement>;
|
|
23
|
+
role: AriaRole | undefined;
|
|
24
|
+
'aria-label': string | undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The region's ref, role and accessible name. While the region may scroll
|
|
28
|
+
* and its content overflows, it is a group named by the caller's `label`, or
|
|
29
|
+
* by the translated "Content". A caller's `role` is kept with only the
|
|
30
|
+
* caller's `label`: a single main landmark relies on its role alone. When
|
|
31
|
+
* the region does not scroll, the caller's own `role` and `label` pass
|
|
32
|
+
* through unchanged.
|
|
33
|
+
*/
|
|
34
|
+
export declare function useScrollRegionProps({ isScrollable, role, label, }: ScrollRegionOptions): ScrollRegionProps;
|
|
35
|
+
//# sourceMappingURL=useScrollRegionProps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useScrollRegionProps.d.ts","sourceRoot":"","sources":["../../src/hooks/useScrollRegionProps.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH,OAAO,EAAwB,KAAK,QAAQ,EAAE,KAAK,WAAW,EAAC,MAAM,OAAO,CAAC;AAU7E,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,YAAY,EAAE,OAAO,CAAC;IACtB,oEAAoE;IACpE,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,WAAW,CAAC,WAAW,CAAC,CAAC;IAC9B,IAAI,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC3B,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,YAAY,EACZ,IAAI,EACJ,KAAK,GACN,EAAE,mBAAmB,GAAG,iBAAiB,CAsBzC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file useScrollRegionProps.ts
|
|
7
|
+
* @input Whether a region may scroll, and the caller's role and label
|
|
8
|
+
* @output useScrollRegionProps: a ref for the region, and the role and
|
|
9
|
+
* accessible name that the region carries while its content overflows
|
|
10
|
+
* @position Internal hook for LayoutContent, LayoutPanel and StackItem.
|
|
11
|
+
*
|
|
12
|
+
* SYNC: When modified, update:
|
|
13
|
+
* - /packages/core/src/hooks/useScrollRegionProps.test.tsx
|
|
14
|
+
* - /packages/core/src/AppShell/observeMainOverflow.ts
|
|
15
|
+
*/
|
|
16
|
+
import { useCallback, useState } from 'react';
|
|
17
|
+
import { registerEnglishFallback, useComponentTranslator } from "../i18n/useComponentTranslator.js";
|
|
18
|
+
import english from "../i18n/generated-english/hooks/useScrollRegionProps.generated.js";
|
|
19
|
+
import { observeMainOverflow } from "../AppShell/observeMainOverflow.js";
|
|
20
|
+
registerEnglishFallback(english);
|
|
21
|
+
/**
|
|
22
|
+
* The region's ref, role and accessible name. While the region may scroll
|
|
23
|
+
* and its content overflows, it is a group named by the caller's `label`, or
|
|
24
|
+
* by the translated "Content". A caller's `role` is kept with only the
|
|
25
|
+
* caller's `label`: a single main landmark relies on its role alone. When
|
|
26
|
+
* the region does not scroll, the caller's own `role` and `label` pass
|
|
27
|
+
* through unchanged.
|
|
28
|
+
*/
|
|
29
|
+
export function useScrollRegionProps({
|
|
30
|
+
isScrollable,
|
|
31
|
+
role,
|
|
32
|
+
label
|
|
33
|
+
}) {
|
|
34
|
+
const t = useComponentTranslator();
|
|
35
|
+
const [overflows, setOverflows] = useState(false);
|
|
36
|
+
// A caller's role takes no default name, so its region is not observed.
|
|
37
|
+
const hasRole = role != null;
|
|
38
|
+
const ref = useCallback(element => {
|
|
39
|
+
if (element == null || !isScrollable || hasRole) {
|
|
40
|
+
setOverflows(false);
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
return observeMainOverflow(element, setOverflows);
|
|
44
|
+
}, [hasRole, isScrollable]);
|
|
45
|
+
const isGroup = !hasRole && isScrollable && overflows;
|
|
46
|
+
return {
|
|
47
|
+
ref,
|
|
48
|
+
role: role ?? (isGroup ? 'group' : undefined),
|
|
49
|
+
'aria-label': label ?? (isGroup ? t('@astryx.scrollRegion.label') : undefined)
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @generated by scripts/generate-i18n-runtime.mjs from packages/core/locales/en.json,
|
|
3
|
+
* holding the English for the catalog keys named in packages/core/src/hooks/useScrollRegionProps.ts.
|
|
4
|
+
* Do not edit by hand. Run `pnpm generate:i18n-runtime` instead.
|
|
5
|
+
*/
|
|
6
|
+
declare const english: Readonly<Record<string, string>>;
|
|
7
|
+
export default english;
|
|
8
|
+
//# sourceMappingURL=useScrollRegionProps.generated.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useScrollRegionProps.generated.d.ts","sourceRoot":"","sources":["../../../../src/i18n/generated-english/hooks/useScrollRegionProps.generated.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH,QAAA,MAAM,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAE7C,CAAC;AAEF,eAAe,OAAO,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @generated by scripts/generate-i18n-runtime.mjs from packages/core/locales/en.json,
|
|
5
|
+
* holding the English for the catalog keys named in packages/core/src/hooks/useScrollRegionProps.ts.
|
|
6
|
+
* Do not edit by hand. Run `pnpm generate:i18n-runtime` instead.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const english = {
|
|
10
|
+
"@astryx.scrollRegion.label": "Content"
|
|
11
|
+
};
|
|
12
|
+
export default english;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"en.generated.d.ts","sourceRoot":"","sources":["../../../src/i18n/generated-locales/en.generated.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH,QAAA,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"en.generated.d.ts","sourceRoot":"","sources":["../../../src/i18n/generated-locales/en.generated.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH,QAAA,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAoqBpD,CAAC;AAEF,eAAe,cAAc,CAAC"}
|
|
@@ -338,6 +338,7 @@ const runtimeCatalog = {
|
|
|
338
338
|
'@astryx.banner.expand': 'Expand',
|
|
339
339
|
'@astryx.button.loading': 'Loading',
|
|
340
340
|
'@astryx.spinner.loading': 'Loading',
|
|
341
|
+
'@astryx.scrollRegion.label': 'Content',
|
|
341
342
|
'@astryx.chatComposerDrawer.expand': 'Expand {label}',
|
|
342
343
|
'@astryx.chatComposerDrawer.collapse': 'Collapse {label}',
|
|
343
344
|
'@astryx.chatDictationButton.startDictation': 'Start dictation',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pseudo.generated.d.ts","sourceRoot":"","sources":["../../../src/i18n/generated-locales/pseudo.generated.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH,QAAA,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"pseudo.generated.d.ts","sourceRoot":"","sources":["../../../src/i18n/generated-locales/pseudo.generated.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH,QAAA,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAgrBpD,CAAC;AAEF,eAAe,cAAc,CAAC"}
|
|
@@ -338,6 +338,7 @@ const runtimeCatalog = {
|
|
|
338
338
|
'@astryx.banner.expand': '⟦Éẋþàñð⟧',
|
|
339
339
|
'@astryx.button.loading': '⟦Łóàðíñĝ⟧',
|
|
340
340
|
'@astryx.spinner.loading': '⟦Łóàðíñĝ⟧',
|
|
341
|
+
'@astryx.scrollRegion.label': '⟦Çóñţéñţ⟧',
|
|
341
342
|
'@astryx.chatComposerDrawer.expand': '⟦Éẋþàñð {label}⟧',
|
|
342
343
|
'@astryx.chatComposerDrawer.collapse': '⟦Çółłàþšé {label}⟧',
|
|
343
344
|
'@astryx.chatDictationButton.startDictation': '⟦Šţàřţ ðíçţàţíóñ⟧',
|
package/locales/en.json
CHANGED
|
@@ -1323,6 +1323,10 @@
|
|
|
1323
1323
|
"defaultMessage": "Loading",
|
|
1324
1324
|
"description": "Screen-reader-only default name for a standalone Spinner's role=\"status\" element, used when the consumer supplies neither `aria-label` nor a visible string label; also announced once when a busy input or selector starts loading, in place of its decorative Spinner. Present-progressive form. Kept separate from `button.loading` and `typeahead.loading` — translations may diverge."
|
|
1325
1325
|
},
|
|
1326
|
+
"@astryx.scrollRegion.label": {
|
|
1327
|
+
"defaultMessage": "Content",
|
|
1328
|
+
"description": "Accessible name of a scrolling content region (LayoutContent, LayoutPanel or StackItem) when the caller gives none. A screen reader announces it with the region's group role while the region scrolls."
|
|
1329
|
+
},
|
|
1326
1330
|
"@astryx.chatComposerDrawer.expand": {
|
|
1327
1331
|
"defaultMessage": "Expand {label}",
|
|
1328
1332
|
"description": "Screen-reader-only label on the ChatComposerDrawer toggle when the drawer is collapsed. `{label}` is the drawer's visible name — example: `Expand Attachments`. Pairs with `collapse`."
|
package/locales/pseudo.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/core",
|
|
3
|
-
"version": "0.7.9-canary.
|
|
3
|
+
"version": "0.7.9-canary.cb2a34a",
|
|
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.9-canary.
|
|
874
|
-
"@astryxdesign/cli": "0.7.9-canary.
|
|
875
|
-
"@astryxdesign/interaction-harness": "0.7.9-canary.
|
|
873
|
+
"@astryxdesign/a11y-spec": "0.7.9-canary.cb2a34a",
|
|
874
|
+
"@astryxdesign/cli": "0.7.9-canary.cb2a34a",
|
|
875
|
+
"@astryxdesign/interaction-harness": "0.7.9-canary.cb2a34a",
|
|
876
876
|
"@babel/cli": "^7.29.7",
|
|
877
877
|
"@babel/core": "^7.29.7",
|
|
878
878
|
"@babel/preset-react": "^7.29.7",
|
|
@@ -4,20 +4,23 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file observeMainOverflow.ts
|
|
7
|
-
* @input
|
|
8
|
-
*
|
|
7
|
+
* @input A scroll container that holds its content as direct children with
|
|
8
|
+
* no wrapper: AppShell's main region under `height="fill"`, or a scrolling
|
|
9
|
+
* LayoutContent, LayoutPanel or StackItem
|
|
9
10
|
* @output observeMainOverflow(main, onChange): reports whether main's content
|
|
10
11
|
* overflows it on either axis, once at the start and on every change, and
|
|
11
12
|
* returns the function that stops observing
|
|
12
|
-
* @position Internal
|
|
13
|
+
* @position Internal and never exported. AppShell makes main a
|
|
13
14
|
* keyboard stop only while it overflows (`spec:AST-025` FR12,
|
|
14
|
-
* `component:AppShell` FR4 and PR1).
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* `component:AppShell` FR4 and PR1). useScrollRegionProps names a
|
|
16
|
+
* scrolling region only while it overflows. Main has no inner content
|
|
17
|
+
* box, so its content grows inside it without resizing it: its direct
|
|
18
|
+
* children's sizes are watched instead.
|
|
17
19
|
*
|
|
18
20
|
* SYNC: When modified, update:
|
|
19
21
|
* - /packages/core/src/AppShell/AppShell.tsx
|
|
20
22
|
* - /packages/core/src/AppShell/observeMainOverflow.test.ts
|
|
23
|
+
* - /packages/core/src/hooks/useScrollRegionProps.ts
|
|
21
24
|
*/
|
|
22
25
|
|
|
23
26
|
import {
|
|
@@ -24,7 +24,7 @@ export const docs = {
|
|
|
24
24
|
],
|
|
25
25
|
},
|
|
26
26
|
description:
|
|
27
|
-
'Groups an input with prefix
|
|
27
|
+
'Groups an input with prefix and suffix addons in one connected row. A focused member stays above its neighbours, so its focus ring shows in full.',
|
|
28
28
|
props: [
|
|
29
29
|
{
|
|
30
30
|
name: 'children',
|
|
@@ -59,7 +59,7 @@ export const docs = {
|
|
|
59
59
|
{
|
|
60
60
|
name: 'isOptional',
|
|
61
61
|
type: 'boolean',
|
|
62
|
-
description: 'Show "
|
|
62
|
+
description: 'Show the "Optional" indicator after the label.',
|
|
63
63
|
default: 'false',
|
|
64
64
|
},
|
|
65
65
|
{
|
|
@@ -77,7 +77,8 @@ export const docs = {
|
|
|
77
77
|
{
|
|
78
78
|
name: 'status',
|
|
79
79
|
type: "{type: 'warning' | 'error' | 'success', message?: string}",
|
|
80
|
-
description:
|
|
80
|
+
description:
|
|
81
|
+
'Validation status. Its message shows below the group. The group border does not change; a theme can style the row from `data-status`.',
|
|
81
82
|
},
|
|
82
83
|
{
|
|
83
84
|
name: 'labelTooltip',
|
|
@@ -109,7 +109,8 @@ export interface InputGroupProps extends Omit<
|
|
|
109
109
|
size?: InputGroupSize;
|
|
110
110
|
|
|
111
111
|
/**
|
|
112
|
-
*
|
|
112
|
+
* Validation status. Its message shows below the group. The group border
|
|
113
|
+
* does not change; a theme can style the row from `data-status`.
|
|
113
114
|
*/
|
|
114
115
|
status?: InputStatus;
|
|
115
116
|
|
|
@@ -125,8 +126,8 @@ export interface InputGroupProps extends Omit<
|
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
/**
|
|
128
|
-
* Groups an input with prefix
|
|
129
|
-
*
|
|
129
|
+
* Groups an input with prefix and suffix addons in one connected row. A
|
|
130
|
+
* focused member stays above its neighbours, so its focus ring shows in full.
|
|
130
131
|
*
|
|
131
132
|
* @example
|
|
132
133
|
* ```
|
|
@@ -96,8 +96,10 @@ export const groupStyles = stylex.create({
|
|
|
96
96
|
default: 0,
|
|
97
97
|
[IS_LAST_ITEM]: radiusVars['--radius-element'],
|
|
98
98
|
},
|
|
99
|
+
// Input wrappers already sit at z-index 1. A focused member goes one
|
|
100
|
+
// higher, so an unfocused input beside it cannot cover its focus ring.
|
|
99
101
|
':focus-within': {
|
|
100
|
-
zIndex:
|
|
102
|
+
zIndex: 2,
|
|
101
103
|
},
|
|
102
104
|
},
|
|
103
105
|
});
|
|
@@ -30,7 +30,8 @@ export const docs = {
|
|
|
30
30
|
{
|
|
31
31
|
name: 'label',
|
|
32
32
|
type: 'string',
|
|
33
|
-
description:
|
|
33
|
+
description:
|
|
34
|
+
'Accessible label for the landmark element. While the region scrolls, it also names the scrolling region, which otherwise takes a translated "Content".',
|
|
34
35
|
},
|
|
35
36
|
{
|
|
36
37
|
name: 'role',
|
|
@@ -66,7 +67,8 @@ export const docsZh = {
|
|
|
66
67
|
{
|
|
67
68
|
name: 'label',
|
|
68
69
|
type: 'string',
|
|
69
|
-
description:
|
|
70
|
+
description:
|
|
71
|
+
'地标元素的无障碍标签。区域滚动时,它也为滚动区域命名;未提供时使用已翻译的“Content”(内容)。',
|
|
70
72
|
},
|
|
71
73
|
{
|
|
72
74
|
name: 'role',
|
|
@@ -86,7 +88,8 @@ export const docsDense = {
|
|
|
86
88
|
padding: 'Internal padding on spacing scale. Overrides layout container default.',
|
|
87
89
|
isScrollable:
|
|
88
90
|
'Enable scrollable overflow. With arithmetic contentWidth values, the scrollport reaches open sides while context-aware insets align children.',
|
|
89
|
-
label:
|
|
91
|
+
label:
|
|
92
|
+
'Accessible label for landmark element; also names the region while it scrolls (default "Content").',
|
|
90
93
|
role: 'ARIA landmark role.',
|
|
91
94
|
},
|
|
92
95
|
};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file LayoutContent.tsx
|
|
7
|
-
* @input Uses React StyleX, LayoutSlotsContext
|
|
7
|
+
* @input Uses React StyleX, LayoutSlotsContext, useScrollRegionProps
|
|
8
8
|
* @output Exports LayoutContent component and LayoutContentProps
|
|
9
9
|
* @position Scrollable main content area for Layout. Wraps the primary body content
|
|
10
10
|
* with automatic scroll containment and context-aware padding.
|
|
@@ -21,6 +21,8 @@ import {use} from 'react';
|
|
|
21
21
|
import * as stylex from '@stylexjs/stylex';
|
|
22
22
|
import {spacingVars} from '../theme/tokens.stylex';
|
|
23
23
|
import {LayoutSlotsContext} from './LayoutSlotsContext';
|
|
24
|
+
import {useMergedRefs} from '../hooks/useMergedRefs';
|
|
25
|
+
import {useScrollRegionProps} from '../hooks/useScrollRegionProps';
|
|
24
26
|
import {mergeProps} from '../utils/mergeProps';
|
|
25
27
|
import type {SpacingStep} from '../utils/types';
|
|
26
28
|
import {themeProps} from '../utils/themeProps';
|
|
@@ -132,6 +134,8 @@ export interface LayoutContentProps extends BaseProps<HTMLDivElement> {
|
|
|
132
134
|
/**
|
|
133
135
|
* Accessible label for the landmark.
|
|
134
136
|
* Required when role is set and multiple landmarks of the same type exist.
|
|
137
|
+
* While the content scrolls, it also names the scrolling region, which
|
|
138
|
+
* otherwise takes a translated "Content".
|
|
135
139
|
*/
|
|
136
140
|
label?: string;
|
|
137
141
|
|
|
@@ -192,12 +196,14 @@ export function LayoutContent({
|
|
|
192
196
|
const {hasHeader, hasFooter, hasStart, hasEnd} = use(LayoutSlotsContext);
|
|
193
197
|
|
|
194
198
|
const isZeroPadding = padding === 0;
|
|
199
|
+
const region = useScrollRegionProps({isScrollable, role, label});
|
|
200
|
+
const mergedRef = useMergedRefs(ref, region.ref);
|
|
195
201
|
|
|
196
202
|
return (
|
|
197
203
|
<div
|
|
198
|
-
ref={
|
|
199
|
-
role={role}
|
|
200
|
-
aria-label={label}
|
|
204
|
+
ref={mergedRef}
|
|
205
|
+
role={region.role}
|
|
206
|
+
aria-label={region['aria-label']}
|
|
201
207
|
{...mergeProps(
|
|
202
208
|
themeProps('layout-content'),
|
|
203
209
|
stylex.props(
|
|
@@ -53,7 +53,8 @@ export const docs = {
|
|
|
53
53
|
{
|
|
54
54
|
name: 'label',
|
|
55
55
|
type: 'string',
|
|
56
|
-
description:
|
|
56
|
+
description:
|
|
57
|
+
'Accessible label for the landmark element. While the region scrolls, it also names the scrolling region, which otherwise takes a translated "Content".',
|
|
57
58
|
},
|
|
58
59
|
{
|
|
59
60
|
name: 'role',
|
|
@@ -106,7 +107,8 @@ export const docsZh = {
|
|
|
106
107
|
{
|
|
107
108
|
name: 'label',
|
|
108
109
|
type: 'string',
|
|
109
|
-
description:
|
|
110
|
+
description:
|
|
111
|
+
'地标元素的无障碍标签。区域滚动时,它也为滚动区域命名;未提供时使用已翻译的“Content”(内容)。',
|
|
110
112
|
},
|
|
111
113
|
{
|
|
112
114
|
name: 'role',
|
|
@@ -139,7 +141,8 @@ export const docsDense = {
|
|
|
139
141
|
padding:
|
|
140
142
|
'Internal padding on spacing scale. Overrides layout container default.',
|
|
141
143
|
isScrollable: 'Enable scrollable overflow.',
|
|
142
|
-
label:
|
|
144
|
+
label:
|
|
145
|
+
'Accessible label for landmark element; also names the region while it scrolls (default "Content").',
|
|
143
146
|
role: 'ARIA landmark role.',
|
|
144
147
|
width:
|
|
145
148
|
'Panel width. Numbers = pixels, strings as-is. Ignored when resizable provided; hook controls width.',
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file LayoutPanel.tsx
|
|
7
|
-
* @input Uses React StyleX, LayoutAreaContext, LayoutSlotsContext
|
|
7
|
+
* @input Uses React StyleX, LayoutAreaContext, LayoutSlotsContext,
|
|
8
|
+
* useScrollRegionProps
|
|
8
9
|
* @output Exports LayoutPanel component and LayoutPanelProps
|
|
9
10
|
* @position Sidebar panel for Layout start/end slots. Use for navigation panels,
|
|
10
11
|
* settings sidebars, detail panels, or any fixed-width side content.
|
|
@@ -22,6 +23,8 @@ import * as stylex from '@stylexjs/stylex';
|
|
|
22
23
|
import {colorVars, spacingVars} from '../theme/tokens.stylex';
|
|
23
24
|
import {LayoutAreaContext} from './LayoutAreaContext';
|
|
24
25
|
import {LayoutSlotsContext} from './LayoutSlotsContext';
|
|
26
|
+
import {useMergedRefs} from '../hooks/useMergedRefs';
|
|
27
|
+
import {useScrollRegionProps} from '../hooks/useScrollRegionProps';
|
|
25
28
|
import {mergeProps} from '../utils';
|
|
26
29
|
import type {SizeValue, SpacingStep} from '../utils/types';
|
|
27
30
|
import type {ResizableProps} from '../Resizable/useResizable';
|
|
@@ -144,6 +147,8 @@ export interface LayoutPanelProps extends BaseProps<HTMLDivElement> {
|
|
|
144
147
|
/**
|
|
145
148
|
* Accessible label for the landmark.
|
|
146
149
|
* Required when role is set and multiple landmarks of the same type exist.
|
|
150
|
+
* While the panel scrolls, it also names the scrolling region, which
|
|
151
|
+
* otherwise takes a translated "Content".
|
|
147
152
|
*/
|
|
148
153
|
label?: string;
|
|
149
154
|
|
|
@@ -235,6 +240,8 @@ export function LayoutPanel({
|
|
|
235
240
|
// When no divider, collapse spacing for seamless visual flow
|
|
236
241
|
const shouldCollapseSpacing =
|
|
237
242
|
!hasDivider && !isZeroPadding && padding == null;
|
|
243
|
+
const region = useScrollRegionProps({isScrollable, role, label});
|
|
244
|
+
const mergedRef = useMergedRefs(ref, region.ref);
|
|
238
245
|
|
|
239
246
|
// Select divider style based on position
|
|
240
247
|
const dividerStyle = isStartPanel
|
|
@@ -252,9 +259,9 @@ export function LayoutPanel({
|
|
|
252
259
|
|
|
253
260
|
return (
|
|
254
261
|
<div
|
|
255
|
-
ref={
|
|
256
|
-
role={role}
|
|
257
|
-
aria-label={label}
|
|
262
|
+
ref={mergedRef}
|
|
263
|
+
role={region.role}
|
|
264
|
+
aria-label={region['aria-label']}
|
|
258
265
|
{...mergeProps(
|
|
259
266
|
themeProps('layout-panel'),
|
|
260
267
|
stylex.props(
|
package/src/Stack/Stack.doc.mjs
CHANGED
|
@@ -524,7 +524,7 @@ export const docs = {
|
|
|
524
524
|
name: 'isScrollable',
|
|
525
525
|
type: 'boolean',
|
|
526
526
|
description:
|
|
527
|
-
'Enables scrollable overflow (overflow: auto). StackItem already applies the flex min-height/min-width reset, so <StackItem size="fill" isScrollable> is a complete scroll region. Matches isScrollable on LayoutContent and LayoutPanel.',
|
|
527
|
+
'Enables scrollable overflow (overflow: auto). StackItem already applies the flex min-height/min-width reset, so <StackItem size="fill" isScrollable> is a complete scroll region. Matches isScrollable on LayoutContent and LayoutPanel. While its content overflows, a div or span item is a group named by aria-label, or by a translated "Content"; another element keeps its own role.',
|
|
528
528
|
default: 'false',
|
|
529
529
|
},
|
|
530
530
|
{
|
package/src/Stack/StackItem.tsx
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @file StackItem.tsx
|
|
5
|
-
* @input Uses React, ElementType, stackItem utility
|
|
5
|
+
* @input Uses React, ElementType, stackItem utility, StackItemScrollRegion
|
|
6
6
|
* @output Exports StackItem polymorphic component and StackItemProps
|
|
7
|
-
* @position Layout/Stack component; uses stackItem.stylex.ts
|
|
7
|
+
* @position Layout/Stack component; uses stackItem.stylex.ts. A scrollable
|
|
8
|
+
* `div` or `span` item renders through StackItemScrollRegion, which names
|
|
9
|
+
* it while it scrolls.
|
|
8
10
|
*
|
|
9
11
|
* SYNC: When modified, update these files to stay in sync:
|
|
10
12
|
* - /packages/core/src/Stack/Stack.doc.mjs
|
|
@@ -21,6 +23,7 @@ import {
|
|
|
21
23
|
} from './stackItem.stylex';
|
|
22
24
|
import {mergeProps} from '../utils/mergeProps';
|
|
23
25
|
import {themeProps} from '../utils/themeProps';
|
|
26
|
+
import {StackItemScrollRegion} from '../hooks/StackItemScrollRegion';
|
|
24
27
|
|
|
25
28
|
const overflowStyles = stylex.create({
|
|
26
29
|
scrollable: {
|
|
@@ -53,7 +56,9 @@ export interface StackItemProps extends BaseProps<HTMLElement> {
|
|
|
53
56
|
* reset, so `<StackItem size="fill" isScrollable>` is a complete scroll
|
|
54
57
|
* region — it grows to fill the stack and scrolls its own overflow with
|
|
55
58
|
* no extra style plumbing. Matches `isScrollable` on `LayoutContent`
|
|
56
|
-
* and `LayoutPanel`.
|
|
59
|
+
* and `LayoutPanel`. While its content overflows, a `div` or `span` item
|
|
60
|
+
* is a group named by the caller's `aria-label`, or by a translated
|
|
61
|
+
* "Content". Another element keeps its own role.
|
|
57
62
|
* @default false
|
|
58
63
|
*/
|
|
59
64
|
isScrollable?: boolean;
|
|
@@ -102,18 +107,27 @@ export function StackItem({
|
|
|
102
107
|
xstyle,
|
|
103
108
|
);
|
|
104
109
|
|
|
110
|
+
const elementProps = {
|
|
111
|
+
...mergeProps(
|
|
112
|
+
themeProps('stack-item', {size}),
|
|
113
|
+
stylexProps,
|
|
114
|
+
className,
|
|
115
|
+
style,
|
|
116
|
+
),
|
|
117
|
+
...props,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
if (isScrollable && (element === 'div' || element === 'span')) {
|
|
121
|
+
return createElement(
|
|
122
|
+
StackItemScrollRegion,
|
|
123
|
+
{as: element, ref, elementProps},
|
|
124
|
+
children,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
105
128
|
return createElement(
|
|
106
129
|
element,
|
|
107
|
-
{
|
|
108
|
-
ref: ref as Ref<Element>,
|
|
109
|
-
...mergeProps(
|
|
110
|
-
themeProps('stack-item', {size}),
|
|
111
|
-
stylexProps,
|
|
112
|
-
className,
|
|
113
|
-
style,
|
|
114
|
-
),
|
|
115
|
-
...props,
|
|
116
|
-
},
|
|
130
|
+
{ref: ref as Ref<Element>, ...elementProps},
|
|
117
131
|
children,
|
|
118
132
|
);
|
|
119
133
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file StackItemScrollRegion.tsx
|
|
7
|
+
* @input A scrollable `div` or `span` StackItem's props, ref and children
|
|
8
|
+
* @output StackItemScrollRegion (internal; not re-exported from
|
|
9
|
+
* hooks/index.ts), which renders that element and names it as a group
|
|
10
|
+
* while its content overflows
|
|
11
|
+
* @position Internal client island for StackItem, which itself stays a
|
|
12
|
+
* static render. Only a scrollable `div` or `span` item measures its
|
|
13
|
+
* overflow, on the client; another element keeps its own role.
|
|
14
|
+
*
|
|
15
|
+
* SYNC: When modified, update:
|
|
16
|
+
* - /packages/core/src/Stack/StackItem.tsx
|
|
17
|
+
* - /packages/core/src/serverSafeComponents.test.ts
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import {createElement, type AriaRole, type ReactNode, type Ref} from 'react';
|
|
21
|
+
import {useMergedRefs} from './useMergedRefs';
|
|
22
|
+
import {useScrollRegionProps} from './useScrollRegionProps';
|
|
23
|
+
|
|
24
|
+
export interface StackItemScrollRegionProps {
|
|
25
|
+
as: 'div' | 'span';
|
|
26
|
+
ref?: Ref<HTMLElement>;
|
|
27
|
+
elementProps: {
|
|
28
|
+
role?: AriaRole;
|
|
29
|
+
'aria-label'?: string;
|
|
30
|
+
[prop: string]: unknown;
|
|
31
|
+
};
|
|
32
|
+
children?: ReactNode;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function StackItemScrollRegion({
|
|
36
|
+
as: element,
|
|
37
|
+
ref,
|
|
38
|
+
elementProps,
|
|
39
|
+
children,
|
|
40
|
+
}: StackItemScrollRegionProps) {
|
|
41
|
+
const region = useScrollRegionProps({
|
|
42
|
+
isScrollable: true,
|
|
43
|
+
role: elementProps.role,
|
|
44
|
+
label: elementProps['aria-label'],
|
|
45
|
+
});
|
|
46
|
+
const mergedRef = useMergedRefs(ref, region.ref);
|
|
47
|
+
return createElement(
|
|
48
|
+
element,
|
|
49
|
+
{
|
|
50
|
+
...elementProps,
|
|
51
|
+
ref: mergedRef,
|
|
52
|
+
role: region.role,
|
|
53
|
+
'aria-label': region['aria-label'],
|
|
54
|
+
},
|
|
55
|
+
children,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file useScrollRegionProps.ts
|
|
7
|
+
* @input Whether a region may scroll, and the caller's role and label
|
|
8
|
+
* @output useScrollRegionProps: a ref for the region, and the role and
|
|
9
|
+
* accessible name that the region carries while its content overflows
|
|
10
|
+
* @position Internal hook for LayoutContent, LayoutPanel and StackItem.
|
|
11
|
+
*
|
|
12
|
+
* SYNC: When modified, update:
|
|
13
|
+
* - /packages/core/src/hooks/useScrollRegionProps.test.tsx
|
|
14
|
+
* - /packages/core/src/AppShell/observeMainOverflow.ts
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {useCallback, useState, type AriaRole, type RefCallback} from 'react';
|
|
18
|
+
import {
|
|
19
|
+
registerEnglishFallback,
|
|
20
|
+
useComponentTranslator,
|
|
21
|
+
} from '../i18n/useComponentTranslator';
|
|
22
|
+
import english from '../i18n/generated-english/hooks/useScrollRegionProps.generated';
|
|
23
|
+
import {observeMainOverflow} from '../AppShell/observeMainOverflow';
|
|
24
|
+
|
|
25
|
+
registerEnglishFallback(english);
|
|
26
|
+
|
|
27
|
+
export interface ScrollRegionOptions {
|
|
28
|
+
/** Whether the region may scroll at all (`overflow: auto`). */
|
|
29
|
+
isScrollable: boolean;
|
|
30
|
+
/** The caller's role. It always wins, and takes no default name. */
|
|
31
|
+
role?: AriaRole;
|
|
32
|
+
/** The caller's accessible name. It always wins. */
|
|
33
|
+
label?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ScrollRegionProps {
|
|
37
|
+
ref: RefCallback<HTMLElement>;
|
|
38
|
+
role: AriaRole | undefined;
|
|
39
|
+
'aria-label': string | undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The region's ref, role and accessible name. While the region may scroll
|
|
44
|
+
* and its content overflows, it is a group named by the caller's `label`, or
|
|
45
|
+
* by the translated "Content". A caller's `role` is kept with only the
|
|
46
|
+
* caller's `label`: a single main landmark relies on its role alone. When
|
|
47
|
+
* the region does not scroll, the caller's own `role` and `label` pass
|
|
48
|
+
* through unchanged.
|
|
49
|
+
*/
|
|
50
|
+
export function useScrollRegionProps({
|
|
51
|
+
isScrollable,
|
|
52
|
+
role,
|
|
53
|
+
label,
|
|
54
|
+
}: ScrollRegionOptions): ScrollRegionProps {
|
|
55
|
+
const t = useComponentTranslator();
|
|
56
|
+
const [overflows, setOverflows] = useState(false);
|
|
57
|
+
// A caller's role takes no default name, so its region is not observed.
|
|
58
|
+
const hasRole = role != null;
|
|
59
|
+
const ref = useCallback(
|
|
60
|
+
(element: HTMLElement | null) => {
|
|
61
|
+
if (element == null || !isScrollable || hasRole) {
|
|
62
|
+
setOverflows(false);
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
return observeMainOverflow(element, setOverflows);
|
|
66
|
+
},
|
|
67
|
+
[hasRole, isScrollable],
|
|
68
|
+
);
|
|
69
|
+
const isGroup = !hasRole && isScrollable && overflows;
|
|
70
|
+
return {
|
|
71
|
+
ref,
|
|
72
|
+
role: role ?? (isGroup ? 'group' : undefined),
|
|
73
|
+
'aria-label':
|
|
74
|
+
label ?? (isGroup ? t('@astryx.scrollRegion.label') : undefined),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @generated by scripts/generate-i18n-runtime.mjs from packages/core/locales/en.json,
|
|
5
|
+
* holding the English for the catalog keys named in packages/core/src/hooks/useScrollRegionProps.ts.
|
|
6
|
+
* Do not edit by hand. Run `pnpm generate:i18n-runtime` instead.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const english: Readonly<Record<string, string>> = {
|
|
10
|
+
"@astryx.scrollRegion.label": "Content"
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default english;
|
|
@@ -375,6 +375,7 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
375
375
|
'@astryx.banner.expand': 'Expand',
|
|
376
376
|
'@astryx.button.loading': 'Loading',
|
|
377
377
|
'@astryx.spinner.loading': 'Loading',
|
|
378
|
+
'@astryx.scrollRegion.label': 'Content',
|
|
378
379
|
'@astryx.chatComposerDrawer.expand': 'Expand {label}',
|
|
379
380
|
'@astryx.chatComposerDrawer.collapse': 'Collapse {label}',
|
|
380
381
|
'@astryx.chatDictationButton.startDictation': 'Start dictation',
|
|
@@ -383,6 +383,7 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
383
383
|
'@astryx.banner.expand': '⟦Éẋþàñð⟧',
|
|
384
384
|
'@astryx.button.loading': '⟦Łóàðíñĝ⟧',
|
|
385
385
|
'@astryx.spinner.loading': '⟦Łóàðíñĝ⟧',
|
|
386
|
+
'@astryx.scrollRegion.label': '⟦Çóñţéñţ⟧',
|
|
386
387
|
'@astryx.chatComposerDrawer.expand': '⟦Éẋþàñð {label}⟧',
|
|
387
388
|
'@astryx.chatComposerDrawer.collapse': '⟦Çółłàþšé {label}⟧',
|
|
388
389
|
'@astryx.chatDictationButton.startDictation': '⟦Šţàřţ ðíçţàţíóñ⟧',
|