@stealthscale/component-screen 0.0.0-stage → 0.2.1
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/LICENSE +21 -0
- package/README.md +965 -2
- package/dist/cyclonedx/bom.json +1 -0
- package/dist/index.d.ts +2295 -0
- package/dist/index.js +8217 -0
- package/dist/recipe-DRMsitrw.js +283 -0
- package/dist/recipe-sZAQY6Kq.js +2623 -0
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/root-DDwipXru.d.ts +327 -0
- package/dist/root-qEh4u7y5.js +1182 -0
- package/dist/shell.d.ts +42 -0
- package/dist/shell.js +166 -0
- package/dist/theme.d.ts +12 -0
- package/dist/theme.js +28 -0
- package/package.json +114 -3
|
@@ -0,0 +1,2623 @@
|
|
|
1
|
+
import { i as NARROW$1, n as FOLDING } from "./recipe-DRMsitrw.js";
|
|
2
|
+
import { CONTROL_INSET_END, CONTROL_INSET_START, axis, below, cornerVariants, defineSlotRecipe, dense, divider, interactive, motion, onSlot, onSlots, paletteVariants, sizeVariants, surface, truncate } from "@stealthscale/theme/authoring";
|
|
3
|
+
//#region src/action-bar/recipe.ts
|
|
4
|
+
/**
|
|
5
|
+
* Styles the action bar, a panel fixed to the bottom of the window with the actions for what a
|
|
6
|
+
* person has selected.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* The root has `display: contents`, because the positioner is fixed to the window and a slot
|
|
10
|
+
* recipe passes its variants from an ancestor. The positioner spans the window inside the inset
|
|
11
|
+
* and takes no pointer events, so the page around the bar remains pressable. The toolbar inside
|
|
12
|
+
* the bar is as wide as the bar, which is at least `sizes.2xl`, 672px. The toolbar folds when it
|
|
13
|
+
* is narrower than the `sm` breakpoint, 640px, and while its controls need more room than the
|
|
14
|
+
* window leaves the bar. The minimum width makes it fold on the width of the window and not on
|
|
15
|
+
* the width of its folded content. The recipe has no `palette` axis,
|
|
16
|
+
* because the bar reads the neutral `bg.panel`, and no `effect` axis, because the bar is not a
|
|
17
|
+
* control.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Distance of the bar from the edges of the window.
|
|
21
|
+
*/
|
|
22
|
+
const OFFSET = "{spacing.inset.lg}";
|
|
23
|
+
/**
|
|
24
|
+
* Places the bar may take along the bottom of the window, in the order a line reads them.
|
|
25
|
+
*/
|
|
26
|
+
const PLACEMENTS = [
|
|
27
|
+
"bottom-start",
|
|
28
|
+
"bottom",
|
|
29
|
+
"bottom-end"
|
|
30
|
+
];
|
|
31
|
+
/**
|
|
32
|
+
* Maps each placement to where the positioner puts the bar along its row.
|
|
33
|
+
*/
|
|
34
|
+
const ALIGNED = {
|
|
35
|
+
bottom: "center",
|
|
36
|
+
"bottom-end": "flex-end",
|
|
37
|
+
"bottom-start": "flex-start"
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Defines the action bar recipe, which places the bar in the middle of the bottom edge by default.
|
|
41
|
+
*/
|
|
42
|
+
const recipe$7 = defineSlotRecipe({
|
|
43
|
+
base: {
|
|
44
|
+
closeTrigger: { flexShrink: "0" },
|
|
45
|
+
content: {
|
|
46
|
+
...motion("sheet.bottom.in", "sheet.bottom.out"),
|
|
47
|
+
background: "bg.panel",
|
|
48
|
+
borderColor: "transparent",
|
|
49
|
+
borderRadius: "l3",
|
|
50
|
+
borderStyle: "solid",
|
|
51
|
+
borderWidth: "hairline",
|
|
52
|
+
boxShadow: "lg",
|
|
53
|
+
color: "fg",
|
|
54
|
+
inlineSize: "fit-content",
|
|
55
|
+
maxInlineSize: "full",
|
|
56
|
+
minInlineSize: "min(100%, {sizes.2xl})",
|
|
57
|
+
padding: dense("{spacing.inset.xs}"),
|
|
58
|
+
pointerEvents: "auto"
|
|
59
|
+
},
|
|
60
|
+
positioner: {
|
|
61
|
+
display: "flex",
|
|
62
|
+
insetBlockEnd: `calc({spacing.safe.bottom} + ${OFFSET})`,
|
|
63
|
+
insetInline: "0",
|
|
64
|
+
paddingInline: OFFSET,
|
|
65
|
+
pointerEvents: "none",
|
|
66
|
+
position: "fixed",
|
|
67
|
+
zIndex: "banner"
|
|
68
|
+
},
|
|
69
|
+
root: { display: "contents" }
|
|
70
|
+
},
|
|
71
|
+
className: "action-bar",
|
|
72
|
+
defaultVariants: { placement: "bottom" },
|
|
73
|
+
jsx: [/^ActionBar(\.\w+)?$/u],
|
|
74
|
+
slots: [
|
|
75
|
+
"root",
|
|
76
|
+
"positioner",
|
|
77
|
+
"content",
|
|
78
|
+
"closeTrigger"
|
|
79
|
+
],
|
|
80
|
+
variants: {
|
|
81
|
+
/**
|
|
82
|
+
* Place of the bar along the bottom of the window: at the start, in the middle or at the end.
|
|
83
|
+
*
|
|
84
|
+
* @remarks
|
|
85
|
+
* Start and end follow the writing direction.
|
|
86
|
+
*/
|
|
87
|
+
placement: onSlot("positioner", axis(PLACEMENTS, (placement) => ({ justifyContent: ALIGNED[placement] }))()) }
|
|
88
|
+
});
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/resize/widths.ts
|
|
91
|
+
/**
|
|
92
|
+
* Attribute an element has while a drag inside it resizes a panel or a column.
|
|
93
|
+
*/
|
|
94
|
+
const RESIZING = "data-resizing";
|
|
95
|
+
/**
|
|
96
|
+
* Returns the CSS length of a width a person resized an element to: the width, and never wider than
|
|
97
|
+
* its limit, so a width stored on a wide screen fits a narrow one.
|
|
98
|
+
*
|
|
99
|
+
* @param width - The width, in pixels.
|
|
100
|
+
* @param max - The widest width, in pixels. Half the window where left out.
|
|
101
|
+
* @returns The length, such as `min(320px, 50vw)`.
|
|
102
|
+
*/
|
|
103
|
+
function lengthOf(width, max) {
|
|
104
|
+
const limit = max === void 0 ? "50vw" : `${String(max)}px`;
|
|
105
|
+
return `min(${String(width)}px, ${limit})`;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Returns a width held between the bounds.
|
|
109
|
+
*
|
|
110
|
+
* @param width - The width asked for.
|
|
111
|
+
* @param bounds - The narrowest and the widest width.
|
|
112
|
+
* @returns The width, raised to the narrowest or lowered to the widest.
|
|
113
|
+
*/
|
|
114
|
+
function clamped(width, { max, min }) {
|
|
115
|
+
return Math.min(Math.max(width, min), max);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Returns 1 where the pointer moving to the physical right widens the element, and -1 where it
|
|
119
|
+
* narrows it.
|
|
120
|
+
*
|
|
121
|
+
* @param edge - Side of the page the element is on.
|
|
122
|
+
* @param rtl - Whether the page reads right to left.
|
|
123
|
+
*/
|
|
124
|
+
function growthOf(edge, rtl) {
|
|
125
|
+
return edge === "start" === !rtl ? 1 : -1;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Returns the width a key moves the element to.
|
|
129
|
+
*
|
|
130
|
+
* @param key - The key pressed, as `KeyboardEvent.key` spells it.
|
|
131
|
+
* @param shift - Whether Shift is held.
|
|
132
|
+
* @param width - The element's width now.
|
|
133
|
+
* @param scale - The narrowest and the widest width, and the direction the width grows in.
|
|
134
|
+
* @returns The new width, or undefined for a key that resizes nothing.
|
|
135
|
+
*/
|
|
136
|
+
function keyedWidth(key, shift, width, scale) {
|
|
137
|
+
const step = (shift ? 50 : 10) * scale.growth;
|
|
138
|
+
if (key === "ArrowRight") return clamped(width + step, scale);
|
|
139
|
+
if (key === "ArrowLeft") return clamped(width - step, scale);
|
|
140
|
+
if (key === "Home") return scale.min;
|
|
141
|
+
if (key === "End") return scale.max;
|
|
142
|
+
}
|
|
143
|
+
//#endregion
|
|
144
|
+
//#region src/resize/strip.ts
|
|
145
|
+
/**
|
|
146
|
+
* Styles the strip on the edge of a panel or a column that a person presses, drags or focuses.
|
|
147
|
+
*
|
|
148
|
+
* @remarks
|
|
149
|
+
* The strip is 24px wide and transparent, with a line down its middle under the pointer, on
|
|
150
|
+
* keyboard focus and while a drag inside its holder runs. A separator, the strip of an element a
|
|
151
|
+
* person resizes, takes the column resize cursor. The strip is hidden under a coarse pointer,
|
|
152
|
+
* where a finger presses the menu button instead. The module imports no React, because the theme
|
|
153
|
+
* plugin evaluates the recipes that read it in Node.
|
|
154
|
+
*/
|
|
155
|
+
/**
|
|
156
|
+
* Styles the strip, with its placement left to the recipe that reads it.
|
|
157
|
+
*/
|
|
158
|
+
const STRIP$3 = {
|
|
159
|
+
...interactive(),
|
|
160
|
+
_after: {
|
|
161
|
+
background: "transparent",
|
|
162
|
+
content: "\"\"",
|
|
163
|
+
inlineSize: "{borderWidths.indicator}",
|
|
164
|
+
insetBlock: "0",
|
|
165
|
+
insetInlineStart: "calc(50% - {borderWidths.indicator} / 2)",
|
|
166
|
+
position: "absolute"
|
|
167
|
+
},
|
|
168
|
+
_focusVisible: { _after: { background: "colorPalette.focusRing" } },
|
|
169
|
+
_hover: { _after: { background: "border.emphasized" } },
|
|
170
|
+
_touch: { display: "none" },
|
|
171
|
+
"&[role=separator]": { cursor: "resizeColumn" },
|
|
172
|
+
[`[${RESIZING}] &`]: { _after: { background: "border.emphasized" } },
|
|
173
|
+
appearance: "none",
|
|
174
|
+
background: "transparent",
|
|
175
|
+
borderWidth: "0",
|
|
176
|
+
colorPalette: "neutral",
|
|
177
|
+
inlineSize: "6",
|
|
178
|
+
padding: "0",
|
|
179
|
+
zIndex: "docked"
|
|
180
|
+
};
|
|
181
|
+
//#endregion
|
|
182
|
+
//#region src/app-shell/metrics.ts
|
|
183
|
+
/**
|
|
184
|
+
* Names the custom properties and attributes the app shell reads, and defines the style objects
|
|
185
|
+
* its recipe is written from.
|
|
186
|
+
*
|
|
187
|
+
* @remarks
|
|
188
|
+
* The module keeps the recipe under the 300-line limit. It imports no React, because the theme
|
|
189
|
+
* plugin evaluates the recipe in Node.
|
|
190
|
+
*/
|
|
191
|
+
/**
|
|
192
|
+
* The recipe's class name, used to build the selectors from one part to another.
|
|
193
|
+
*
|
|
194
|
+
* @remarks
|
|
195
|
+
* The binding writes one class per part, such as `app-shell__main`, and no part attribute, so a
|
|
196
|
+
* selector across parts targets the class built from this constant.
|
|
197
|
+
*/
|
|
198
|
+
const CLASS$5 = "app-shell";
|
|
199
|
+
/**
|
|
200
|
+
* Custom property with the width a panel opens to.
|
|
201
|
+
*/
|
|
202
|
+
const PANEL_SIZE = "--app-shell-panel-size";
|
|
203
|
+
/**
|
|
204
|
+
* Custom property with the width a panel closes to.
|
|
205
|
+
*/
|
|
206
|
+
const PANEL_RAIL = "--app-shell-panel-rail";
|
|
207
|
+
/**
|
|
208
|
+
* Custom property with the height of the pinned bars above a pinned bar.
|
|
209
|
+
*/
|
|
210
|
+
const STICKY_OFFSET$1 = "--app-shell-sticky-offset";
|
|
211
|
+
/**
|
|
212
|
+
* Custom property on the root with the height of every pinned bar, the bars in a pane included,
|
|
213
|
+
* which a page's sticky bands stick under.
|
|
214
|
+
*/
|
|
215
|
+
const STICKY_TOP$1 = "--app-shell-sticky-top";
|
|
216
|
+
/**
|
|
217
|
+
* Custom property on the root with the height of the pinned bars above the body, which a panel
|
|
218
|
+
* that sticks under them reads.
|
|
219
|
+
*
|
|
220
|
+
* @remarks
|
|
221
|
+
* A bar in a pane is beside the panels and not above them, so a panel sticks under the root's
|
|
222
|
+
* own bars alone.
|
|
223
|
+
*/
|
|
224
|
+
const PANEL_TOP = "--app-shell-panel-top";
|
|
225
|
+
/**
|
|
226
|
+
* Custom property on the root with the height of the pinned bars in a pane.
|
|
227
|
+
*/
|
|
228
|
+
const PANE_TOP = "--app-shell-pane-top";
|
|
229
|
+
/**
|
|
230
|
+
* Custom property an ancestor sets to the height of the shell's window.
|
|
231
|
+
*
|
|
232
|
+
* @remarks
|
|
233
|
+
* A box that stages a shell at a fixed height sets it, and also contains the shell's fixed
|
|
234
|
+
* parts with `contain: layout`. Unset, the window is the viewport.
|
|
235
|
+
*/
|
|
236
|
+
const WINDOW_HEIGHT = "--app-shell-window-height";
|
|
237
|
+
/**
|
|
238
|
+
* Height of the shell's window: the ancestor's `WINDOW_HEIGHT`, or the dynamic viewport height.
|
|
239
|
+
*/
|
|
240
|
+
const WINDOW = `var(${WINDOW_HEIGHT}, 100dvh)`;
|
|
241
|
+
/**
|
|
242
|
+
* Attribute the root writes after its first paint.
|
|
243
|
+
*
|
|
244
|
+
* @remarks
|
|
245
|
+
* A panel measures the shell after its first render and may open, close or leave the body. No
|
|
246
|
+
* part transitions before the root has this attribute, so a panel takes its measured state
|
|
247
|
+
* without a slide on load.
|
|
248
|
+
*/
|
|
249
|
+
const SETTLED = "data-settled";
|
|
250
|
+
/**
|
|
251
|
+
* Styles the transition of a moving part, with no motion under reduced motion, before the root has
|
|
252
|
+
* settled, or while a person drags a panel's width.
|
|
253
|
+
*/
|
|
254
|
+
const MOVING = {
|
|
255
|
+
_motionReduce: { transitionDuration: "0s" },
|
|
256
|
+
transitionDuration: "move",
|
|
257
|
+
transitionTimingFunction: "move",
|
|
258
|
+
[`.${CLASS$5}__root:not([${SETTLED}]) &`]: { transitionDuration: "0s" },
|
|
259
|
+
[`.${CLASS$5}__root[${RESIZING}] &`]: { transitionDuration: "0s" }
|
|
260
|
+
};
|
|
261
|
+
/**
|
|
262
|
+
* Width of a panel over the page: its open width, and never closer than a rail's width to the far
|
|
263
|
+
* edge of the window.
|
|
264
|
+
*
|
|
265
|
+
* @remarks
|
|
266
|
+
* The percentage resolves against the sheet's containing block, which is the viewport, or a box
|
|
267
|
+
* with `contain: layout` that stages the shell.
|
|
268
|
+
*/
|
|
269
|
+
const SHEET = `min(var(${PANEL_SIZE}), calc(100% - {sizes.rail}))`;
|
|
270
|
+
/**
|
|
271
|
+
* Styles a panel over the page: fixed to the window, a rail's width short of the far edge, and
|
|
272
|
+
* inside the safe area.
|
|
273
|
+
*
|
|
274
|
+
* @remarks
|
|
275
|
+
* The panel is fixed to the window, so one rule applies to both scroll modes. A closed sheet
|
|
276
|
+
* keeps its width and slides out. Its visibility changes with no duration: a closing sheet
|
|
277
|
+
* becomes hidden after a delay as long as the slide, and an opening sheet becomes visible at
|
|
278
|
+
* once. The shell moves focus into a sheet in the commit that opens it, and a browser does not
|
|
279
|
+
* focus a hidden element. A sheet is a raised surface over the backdrop: the panel ground and the
|
|
280
|
+
* `lg` shadow. A sidebar inside it paints its own ground over the panel's.
|
|
281
|
+
*/
|
|
282
|
+
const OVERLAID = {
|
|
283
|
+
_motionReduce: { transitionDelay: "0s" },
|
|
284
|
+
background: "bg.panel",
|
|
285
|
+
blockSize: WINDOW,
|
|
286
|
+
boxShadow: "lg",
|
|
287
|
+
inlineSize: SHEET,
|
|
288
|
+
insetBlock: "0",
|
|
289
|
+
paddingBlockEnd: "safe.bottom",
|
|
290
|
+
paddingBlockStart: "safe.top",
|
|
291
|
+
position: "fixed",
|
|
292
|
+
transitionDelay: "0s",
|
|
293
|
+
transitionDuration: "{durations.move}, 0s",
|
|
294
|
+
transitionProperty: "translate, visibility",
|
|
295
|
+
zIndex: "modal"
|
|
296
|
+
};
|
|
297
|
+
/**
|
|
298
|
+
* Styles a closed sheet on either side: it keeps its open width, and the visibility changes after
|
|
299
|
+
* the slide has ended.
|
|
300
|
+
*
|
|
301
|
+
* @remarks
|
|
302
|
+
* The width is written again here, because the panel's closed width is an equally specific rule
|
|
303
|
+
* that the compiler emits after the sheet's own.
|
|
304
|
+
*/
|
|
305
|
+
const SLID_OUT = {
|
|
306
|
+
inlineSize: SHEET,
|
|
307
|
+
transitionDelay: "0s, {durations.moderate}"
|
|
308
|
+
};
|
|
309
|
+
/**
|
|
310
|
+
* Styles both panels: a track between the open and closed widths.
|
|
311
|
+
*
|
|
312
|
+
* @remarks
|
|
313
|
+
* A panel closed to nothing becomes hidden after its transition, so a border the application
|
|
314
|
+
* gives it is not visible. The shell sets the visibility, and the application sets the border.
|
|
315
|
+
*/
|
|
316
|
+
const PANEL = {
|
|
317
|
+
...MOVING,
|
|
318
|
+
display: "flex",
|
|
319
|
+
flexShrink: "0",
|
|
320
|
+
inlineSize: `var(${PANEL_SIZE})`,
|
|
321
|
+
overflow: "hidden",
|
|
322
|
+
[PANEL_RAIL]: "0px",
|
|
323
|
+
position: "relative",
|
|
324
|
+
transitionProperty: "inline-size, visibility",
|
|
325
|
+
"&[data-collapse=hide][data-state=closed]": { visibility: "hidden" },
|
|
326
|
+
"&[data-collapse=icons]": { [PANEL_RAIL]: "sizes.rail" },
|
|
327
|
+
"&[data-stacked]": {
|
|
328
|
+
flexBasis: "100%",
|
|
329
|
+
inlineSize: "100%",
|
|
330
|
+
order: "1",
|
|
331
|
+
position: "static"
|
|
332
|
+
},
|
|
333
|
+
"&[data-state=closed]": { inlineSize: `var(${PANEL_RAIL})` }
|
|
334
|
+
};
|
|
335
|
+
/**
|
|
336
|
+
* Styles a bar pinned to the window, filled with `bg.panel`.
|
|
337
|
+
*
|
|
338
|
+
* @remarks
|
|
339
|
+
* The page scrolls under a pinned bar, so the bar has a fill. The fill is `bg.panel`, so the bar
|
|
340
|
+
* reads as a layer over the page.
|
|
341
|
+
*/
|
|
342
|
+
const PINNED = {
|
|
343
|
+
background: "bg.panel",
|
|
344
|
+
position: "sticky",
|
|
345
|
+
zIndex: "sticky"
|
|
346
|
+
};
|
|
347
|
+
/**
|
|
348
|
+
* Styles a panel in the body while the window scrolls: stuck under the root's pinned bars and as
|
|
349
|
+
* tall as the height they leave.
|
|
350
|
+
*
|
|
351
|
+
* @remarks
|
|
352
|
+
* The rule selects a panel beside the page only, because the window-scroll look is a variant and
|
|
353
|
+
* applies over the base, where a sheet is fixed and a panel under the page is static.
|
|
354
|
+
*/
|
|
355
|
+
const STUCK$1 = { ["&:not([data-overlaid], [data-stacked])"]: {
|
|
356
|
+
blockSize: `calc(${WINDOW} - var(${PANEL_TOP}, 0px))`,
|
|
357
|
+
insetBlockStart: `var(${PANEL_TOP}, 0px)`,
|
|
358
|
+
position: "sticky"
|
|
359
|
+
} };
|
|
360
|
+
/**
|
|
361
|
+
* Styles a band of a panel or of the main region: a padded column, growing to the room left with
|
|
362
|
+
* `data-grows`.
|
|
363
|
+
*
|
|
364
|
+
* @remarks
|
|
365
|
+
* A band that scrolls is inside a scroll area whose root is the `scroller`, so the band pads what
|
|
366
|
+
* scrolls and the bar is at the scroller's edge.
|
|
367
|
+
*/
|
|
368
|
+
const SECTION = {
|
|
369
|
+
display: "flex",
|
|
370
|
+
flexDirection: "column",
|
|
371
|
+
flexShrink: "0",
|
|
372
|
+
gap: dense("{spacing.gap.md}"),
|
|
373
|
+
minBlockSize: "0",
|
|
374
|
+
minInlineSize: "0",
|
|
375
|
+
padding: dense("{spacing.gap.lg}"),
|
|
376
|
+
"&[data-grows]": { flex: "1" }
|
|
377
|
+
};
|
|
378
|
+
/**
|
|
379
|
+
* Styles the root of a band that scrolls: it keeps its height, or grows to the room left with
|
|
380
|
+
* `data-grows`, and shrinks below its content, so its viewport scrolls.
|
|
381
|
+
*/
|
|
382
|
+
const SCROLLER = {
|
|
383
|
+
flexShrink: "0",
|
|
384
|
+
minBlockSize: "0",
|
|
385
|
+
minInlineSize: "0",
|
|
386
|
+
"&[data-grows]": { flex: "1" }
|
|
387
|
+
};
|
|
388
|
+
/**
|
|
389
|
+
* Styles the content of a region's scroll area: a column at least as tall as the viewport.
|
|
390
|
+
*
|
|
391
|
+
* @remarks
|
|
392
|
+
* A page grows to the column's height, so a short page still fills the main region. A sidebar
|
|
393
|
+
* in a panel takes the column's height exactly and scrolls its own list, so the header and the
|
|
394
|
+
* footer of the sidebar remain in place.
|
|
395
|
+
*/
|
|
396
|
+
const COLUMN = {
|
|
397
|
+
display: "flex",
|
|
398
|
+
flexDirection: "column",
|
|
399
|
+
minBlockSize: "100%",
|
|
400
|
+
"& > .sidebar__root": {
|
|
401
|
+
flex: "1 1 0",
|
|
402
|
+
minBlockSize: "0"
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
/**
|
|
406
|
+
* Styles the rail: the 24px strip centred on the edge between a panel and the page.
|
|
407
|
+
*
|
|
408
|
+
* @remarks
|
|
409
|
+
* The negative margins take the strip's width back from the row, so the strip overlaps the panel
|
|
410
|
+
* and the page by 12px each and moves neither.
|
|
411
|
+
*/
|
|
412
|
+
const RAIL = {
|
|
413
|
+
...STRIP$3,
|
|
414
|
+
alignSelf: "stretch",
|
|
415
|
+
flexShrink: "0",
|
|
416
|
+
marginInline: "calc({sizes.6} / -2)",
|
|
417
|
+
position: "relative"
|
|
418
|
+
};
|
|
419
|
+
//#endregion
|
|
420
|
+
//#region src/app-shell/recipe.ts
|
|
421
|
+
/**
|
|
422
|
+
* Declares the app shell's slot recipe, which lays out a column of bars with a body between them
|
|
423
|
+
* and a panel on either side of the page in the body.
|
|
424
|
+
*
|
|
425
|
+
* @remarks
|
|
426
|
+
* The recipe has nineteen slots. The body, the main region, a panel's content and a band that
|
|
427
|
+
* scrolls are the primitives package's scroll areas, so every bar in the shell is the theme's:
|
|
428
|
+
* `bodyViewport` and `row` are the body's viewport and content, `mainViewport` is the `main`
|
|
429
|
+
* element, `column` is the content of the main region and of a panel, and `scroller` is the root
|
|
430
|
+
* of a band that scrolls. A pane is a column in the row between the panels, with bars above and
|
|
431
|
+
* below the main region, so the panels run the full height beside the bars. A part that is left
|
|
432
|
+
* out takes no room. Every part is a container: the recipe sets the layout, the scrolling, the
|
|
433
|
+
* motion, the hairlines between regions and the grounds, and the application styles the rest
|
|
434
|
+
* through a theme. The plain look fills no region, so the shell shows the ground it is placed
|
|
435
|
+
* on. The backdrop, a sheet and a pinned bar are filled, because the page is visible under each.
|
|
436
|
+
* A sheet is raised by a shadow. A panel in the body is a track whose width moves between its
|
|
437
|
+
* open and closed widths, and its content keeps the open width, so the track clips the content
|
|
438
|
+
* instead of reflowing it. A panel over the page is fixed to the window with a backdrop behind
|
|
439
|
+
* it. The open and closed widths are the theme's `sizes.sidebar`, `sizes.aside` and `sizes.rail`
|
|
440
|
+
* unless a panel states its own. The window's height is `100dvh` unless an ancestor sets
|
|
441
|
+
* `WINDOW_HEIGHT`, so a shell staged in a box of a fixed height fills the box. The header and
|
|
442
|
+
* the footer pad their content by the middle gap, the status bar pads its row of entries by the
|
|
443
|
+
* smallest gap above and below and the middle gap at the sides, and takes no room while every
|
|
444
|
+
* entry renders nothing. A section pads by the large gap. The recipe has no `palette` and no
|
|
445
|
+
* `effect` axis, because the parts are containers and the components inside them offer their
|
|
446
|
+
* own.
|
|
447
|
+
*/
|
|
448
|
+
/**
|
|
449
|
+
* Styles a plain, divided shell that scrolls its page.
|
|
450
|
+
*
|
|
451
|
+
* @remarks
|
|
452
|
+
* `staticCss` lists every value of `content`, `scroll` and `variant`. A product's frame states
|
|
453
|
+
* them as data, and the compiler emits a variant only where a source states it as a literal.
|
|
454
|
+
*/
|
|
455
|
+
const recipe$6 = defineSlotRecipe({
|
|
456
|
+
base: {
|
|
457
|
+
aside: {
|
|
458
|
+
...PANEL,
|
|
459
|
+
[PANEL_SIZE]: "sizes.aside",
|
|
460
|
+
"&[data-overlaid]": {
|
|
461
|
+
...OVERLAID,
|
|
462
|
+
"&[data-state=closed]": {
|
|
463
|
+
...SLID_OUT,
|
|
464
|
+
_rtl: { translate: "-100% 0" },
|
|
465
|
+
translate: "100% 0"
|
|
466
|
+
},
|
|
467
|
+
insetInlineEnd: "0"
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
backdrop: {
|
|
471
|
+
...MOVING,
|
|
472
|
+
background: "bg.inverted/40",
|
|
473
|
+
inset: "0",
|
|
474
|
+
opacity: "0",
|
|
475
|
+
pointerEvents: "none",
|
|
476
|
+
position: "fixed",
|
|
477
|
+
transitionProperty: "opacity",
|
|
478
|
+
zIndex: "overlay",
|
|
479
|
+
"&[data-state=open]": {
|
|
480
|
+
opacity: "1",
|
|
481
|
+
pointerEvents: "auto"
|
|
482
|
+
}
|
|
483
|
+
},
|
|
484
|
+
body: {
|
|
485
|
+
flex: "1",
|
|
486
|
+
minBlockSize: "0",
|
|
487
|
+
position: "relative"
|
|
488
|
+
},
|
|
489
|
+
column: COLUMN,
|
|
490
|
+
content: {
|
|
491
|
+
flexShrink: "0",
|
|
492
|
+
inlineSize: `var(${PANEL_SIZE})`,
|
|
493
|
+
minBlockSize: "0",
|
|
494
|
+
"[data-collapse=icons] > &, [data-overlaid] > &, [data-stacked] > &": { inlineSize: "100%" }
|
|
495
|
+
},
|
|
496
|
+
footer: {
|
|
497
|
+
flexShrink: "0",
|
|
498
|
+
padding: dense("{spacing.gap.md}"),
|
|
499
|
+
"&[data-sticky]": {
|
|
500
|
+
...PINNED,
|
|
501
|
+
insetBlockEnd: "0",
|
|
502
|
+
paddingBlockEnd: `calc(${dense("{spacing.gap.md}")} + {spacing.safe.bottom})`
|
|
503
|
+
}
|
|
504
|
+
},
|
|
505
|
+
header: {
|
|
506
|
+
flexShrink: "0",
|
|
507
|
+
padding: dense("{spacing.gap.md}"),
|
|
508
|
+
"&[data-sticky]": {
|
|
509
|
+
...PINNED,
|
|
510
|
+
insetBlockStart: `var(${STICKY_OFFSET$1}, 0px)`
|
|
511
|
+
},
|
|
512
|
+
[`.${CLASS$5}__pane > &`]: { "&[data-sticky]": { insetBlockStart: `calc(var(${PANEL_TOP}, 0px) + var(${STICKY_OFFSET$1}, 0px))` } }
|
|
513
|
+
},
|
|
514
|
+
main: {
|
|
515
|
+
flex: "1",
|
|
516
|
+
minBlockSize: "0",
|
|
517
|
+
minInlineSize: "0"
|
|
518
|
+
},
|
|
519
|
+
navbar: {
|
|
520
|
+
...PANEL,
|
|
521
|
+
[PANEL_SIZE]: "sizes.sidebar",
|
|
522
|
+
"&[data-overlaid]": {
|
|
523
|
+
...OVERLAID,
|
|
524
|
+
"&[data-state=closed]": {
|
|
525
|
+
...SLID_OUT,
|
|
526
|
+
_rtl: { translate: "100% 0" },
|
|
527
|
+
translate: "-100% 0"
|
|
528
|
+
},
|
|
529
|
+
insetInlineStart: "0"
|
|
530
|
+
}
|
|
531
|
+
},
|
|
532
|
+
pane: {
|
|
533
|
+
display: "flex",
|
|
534
|
+
flex: "1",
|
|
535
|
+
flexDirection: "column",
|
|
536
|
+
minBlockSize: "0",
|
|
537
|
+
minInlineSize: "0"
|
|
538
|
+
},
|
|
539
|
+
rail: RAIL,
|
|
540
|
+
root: {
|
|
541
|
+
display: "flex",
|
|
542
|
+
flexDirection: "column",
|
|
543
|
+
inlineSize: "100%",
|
|
544
|
+
[STICKY_TOP$1]: `calc(var(${PANEL_TOP}, 0px) + var(${PANE_TOP}, 0px))`,
|
|
545
|
+
[`&[${RESIZING}]`]: {
|
|
546
|
+
cursor: "resizeColumn",
|
|
547
|
+
userSelect: "none"
|
|
548
|
+
}
|
|
549
|
+
},
|
|
550
|
+
row: {
|
|
551
|
+
display: "flex",
|
|
552
|
+
"&:has(> [data-stacked])": { flexWrap: "wrap" },
|
|
553
|
+
[`&:has(> [data-stacked]) > :is(.${CLASS$5}__main, .${CLASS$5}__pane)`]: { flexBasis: "100%" }
|
|
554
|
+
},
|
|
555
|
+
scroller: SCROLLER,
|
|
556
|
+
section: SECTION,
|
|
557
|
+
status: {
|
|
558
|
+
alignItems: "center",
|
|
559
|
+
columnGap: dense("{spacing.gap.md}"),
|
|
560
|
+
display: "flex",
|
|
561
|
+
flexShrink: "0",
|
|
562
|
+
flexWrap: "wrap",
|
|
563
|
+
paddingBlock: dense("{spacing.gap.xs}"),
|
|
564
|
+
paddingInline: dense("{spacing.gap.md}"),
|
|
565
|
+
rowGap: dense("{spacing.gap.xs}"),
|
|
566
|
+
"&:empty": { display: "none" }
|
|
567
|
+
},
|
|
568
|
+
trigger: { flexShrink: "0" }
|
|
569
|
+
},
|
|
570
|
+
className: CLASS$5,
|
|
571
|
+
defaultVariants: {
|
|
572
|
+
content: "fluid",
|
|
573
|
+
divided: true,
|
|
574
|
+
scroll: "page",
|
|
575
|
+
variant: "plain"
|
|
576
|
+
},
|
|
577
|
+
jsx: [/^AppShell(\.\w+)?$/u],
|
|
578
|
+
slots: [
|
|
579
|
+
"root",
|
|
580
|
+
"header",
|
|
581
|
+
"body",
|
|
582
|
+
"bodyViewport",
|
|
583
|
+
"row",
|
|
584
|
+
"navbar",
|
|
585
|
+
"pane",
|
|
586
|
+
"main",
|
|
587
|
+
"mainViewport",
|
|
588
|
+
"aside",
|
|
589
|
+
"footer",
|
|
590
|
+
"status",
|
|
591
|
+
"content",
|
|
592
|
+
"column",
|
|
593
|
+
"section",
|
|
594
|
+
"scroller",
|
|
595
|
+
"trigger",
|
|
596
|
+
"rail",
|
|
597
|
+
"backdrop"
|
|
598
|
+
],
|
|
599
|
+
staticCss: [{
|
|
600
|
+
content: ["constrained", "fluid"],
|
|
601
|
+
scroll: ["page", "window"],
|
|
602
|
+
variant: [
|
|
603
|
+
"floating",
|
|
604
|
+
"inset",
|
|
605
|
+
"plain"
|
|
606
|
+
]
|
|
607
|
+
}],
|
|
608
|
+
variants: {
|
|
609
|
+
/**
|
|
610
|
+
* Width of the page in the main region: the whole region, or at most the theme's wide page
|
|
611
|
+
* measure, centred in the region.
|
|
612
|
+
*/
|
|
613
|
+
content: {
|
|
614
|
+
constrained: { column: { [`.${CLASS$5}__mainViewport > &`]: {
|
|
615
|
+
marginInline: "auto",
|
|
616
|
+
maxInlineSize: "{sizes.page.wide}"
|
|
617
|
+
} } },
|
|
618
|
+
fluid: { column: { [`.${CLASS$5}__mainViewport > &`]: { maxInlineSize: "none" } } }
|
|
619
|
+
},
|
|
620
|
+
/**
|
|
621
|
+
* Whether a hairline separates each bar and each panel from the page.
|
|
622
|
+
*
|
|
623
|
+
* @remarks
|
|
624
|
+
* The shell renders the lines, because only the shell knows where two of its regions meet. A
|
|
625
|
+
* sidebar inside a panel sets its ground and no line.
|
|
626
|
+
*/
|
|
627
|
+
divided: { true: {
|
|
628
|
+
aside: {
|
|
629
|
+
borderColor: "border",
|
|
630
|
+
borderInlineStartWidth: "hairline"
|
|
631
|
+
},
|
|
632
|
+
footer: {
|
|
633
|
+
borderBlockStartWidth: "hairline",
|
|
634
|
+
borderColor: "border"
|
|
635
|
+
},
|
|
636
|
+
header: {
|
|
637
|
+
borderBlockEndWidth: "hairline",
|
|
638
|
+
borderColor: "border"
|
|
639
|
+
},
|
|
640
|
+
navbar: {
|
|
641
|
+
borderColor: "border",
|
|
642
|
+
borderInlineEndWidth: "hairline"
|
|
643
|
+
},
|
|
644
|
+
status: {
|
|
645
|
+
borderBlockStartWidth: "hairline",
|
|
646
|
+
borderColor: "border"
|
|
647
|
+
}
|
|
648
|
+
} },
|
|
649
|
+
/**
|
|
650
|
+
* What scrolls under the bars: the main region inside a shell the height of the window, or the
|
|
651
|
+
* window itself, with sticky bars pinned to it.
|
|
652
|
+
*
|
|
653
|
+
* @remarks
|
|
654
|
+
* The main region and the body are scroll areas, and this axis turns each viewport's
|
|
655
|
+
* scrolling on or off. While a panel has dropped under the page, the body scrolls the main
|
|
656
|
+
* region and the panel together, so a person scrolls down to the panel under the page. While
|
|
657
|
+
* the window scrolls, neither viewport scrolls, and the browser's own bar scrolls the page.
|
|
658
|
+
*/
|
|
659
|
+
scroll: {
|
|
660
|
+
page: {
|
|
661
|
+
bodyViewport: {
|
|
662
|
+
[`&:has(> .${CLASS$5}__row > [data-stacked])`]: { overflow: "auto" },
|
|
663
|
+
overflow: "visible"
|
|
664
|
+
},
|
|
665
|
+
mainViewport: { [`.${CLASS$5}__row:has(> [data-stacked]) > :is(.${CLASS$5}__main, .${CLASS$5}__pane > .${CLASS$5}__main) > &`]: { overflow: "visible" } },
|
|
666
|
+
root: { blockSize: WINDOW },
|
|
667
|
+
row: {
|
|
668
|
+
"&:has(> [data-stacked])": { blockSize: "auto" },
|
|
669
|
+
blockSize: "100%"
|
|
670
|
+
}
|
|
671
|
+
},
|
|
672
|
+
window: {
|
|
673
|
+
aside: STUCK$1,
|
|
674
|
+
bodyViewport: { overflow: "visible" },
|
|
675
|
+
mainViewport: { overflow: "visible" },
|
|
676
|
+
navbar: STUCK$1,
|
|
677
|
+
root: { minBlockSize: WINDOW },
|
|
678
|
+
row: { alignItems: "flex-start" }
|
|
679
|
+
}
|
|
680
|
+
},
|
|
681
|
+
/**
|
|
682
|
+
* How the page and the panels are set against the root's ground.
|
|
683
|
+
*
|
|
684
|
+
* @remarks
|
|
685
|
+
* `plain` fills nothing, so every region shows the ground the shell is placed on: the page's
|
|
686
|
+
* `bg` in an application, or a staging box's own fill. A sidebar in a panel paints its own
|
|
687
|
+
* ground. `inset` gives the main region `surface()`, the panel ground with a hairline and a
|
|
688
|
+
* shadow, over a `bg.subtle` root, and gives it to the pane instead where the main region is
|
|
689
|
+
* in one, so the bars in the pane are on the card too. `floating` gives each panel's content
|
|
690
|
+
* `surface()` instead.
|
|
691
|
+
*/
|
|
692
|
+
variant: {
|
|
693
|
+
floating: {
|
|
694
|
+
aside: { padding: dense("{spacing.gap.xs}") },
|
|
695
|
+
content: {
|
|
696
|
+
...surface(),
|
|
697
|
+
inlineSize: `calc(var(${PANEL_SIZE}) - {spacing.gap.md})`
|
|
698
|
+
},
|
|
699
|
+
navbar: { padding: dense("{spacing.gap.xs}") },
|
|
700
|
+
root: { background: "bg.subtle" }
|
|
701
|
+
},
|
|
702
|
+
inset: {
|
|
703
|
+
main: { [`&:not(.${CLASS$5}__pane > *)`]: {
|
|
704
|
+
...surface(),
|
|
705
|
+
margin: dense("{spacing.gap.xs}")
|
|
706
|
+
} },
|
|
707
|
+
pane: {
|
|
708
|
+
...surface(),
|
|
709
|
+
margin: dense("{spacing.gap.xs}")
|
|
710
|
+
},
|
|
711
|
+
root: { background: "bg.subtle" }
|
|
712
|
+
},
|
|
713
|
+
plain: { root: { background: "transparent" } }
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
//#endregion
|
|
718
|
+
//#region src/floating-panel/recipe.ts
|
|
719
|
+
/**
|
|
720
|
+
* Recipe for a floating panel: a window over the page that a person drags by its header, resizes
|
|
721
|
+
* from its edges and corners, minimizes, maximizes and closes.
|
|
722
|
+
*
|
|
723
|
+
* @remarks
|
|
724
|
+
* The machine has no root part. The recipe adds a root with `display: contents`, because the
|
|
725
|
+
* trigger and the portalled positioner are siblings and a slot recipe passes its variants from an
|
|
726
|
+
* element above both. The machine fixes the positioner to the window and writes its place and
|
|
727
|
+
* size inline. The positioner stacks at `banner` plus the panel's place in the stack of open
|
|
728
|
+
* panels: above the page's sticky bands, and under a backdrop, a dialog, a popover, a toast and a
|
|
729
|
+
* tooltip. The panel has the dialog's surface, a transparent hairline edge that forced colors
|
|
730
|
+
* paint in `CanvasText`, and a ring outside it while it has keyboard focus. The resize triggers
|
|
731
|
+
* are strips `sizes.2` wide inside the panel's edges, and squares of that side at its corners.
|
|
732
|
+
* The panel does not clip its children, so a corner's square takes the pointer beyond the rounded
|
|
733
|
+
* corner. The machine clips a minimized panel to its header inline. The header pads its controls
|
|
734
|
+
* clear of the strips, and the body's scroll area ends clear of the end and bottom strips, so its
|
|
735
|
+
* bar takes the pointer. The recipe has no `palette` axis, because
|
|
736
|
+
* the panel reads the neutral `bg.panel`, and no `effect` axis, because a panel is not a
|
|
737
|
+
* control.
|
|
738
|
+
*/
|
|
739
|
+
/**
|
|
740
|
+
* Class name of the recipe, which a selector across its parts writes.
|
|
741
|
+
*/
|
|
742
|
+
const CLASS$4 = "floating-panel";
|
|
743
|
+
/**
|
|
744
|
+
* Width of a resize trigger's strip, and the side of a corner's square.
|
|
745
|
+
*/
|
|
746
|
+
const STRIP$2 = "{sizes.2}";
|
|
747
|
+
/**
|
|
748
|
+
* Stacking level of the positioner: the banner level of the z-index scale plus the panel's place in
|
|
749
|
+
* the stack of open panels, which the machine writes as `--z-index`.
|
|
750
|
+
*/
|
|
751
|
+
const LAYERED = "calc({zIndex.banner} + var(--z-index, 0))";
|
|
752
|
+
/**
|
|
753
|
+
* Custom property the primitives scroll area reads on its root for the offset of its focus ring.
|
|
754
|
+
*/
|
|
755
|
+
const RING_OFFSET = "--scroll-area-ring-offset";
|
|
756
|
+
/**
|
|
757
|
+
* Defines the floating panel recipe.
|
|
758
|
+
*/
|
|
759
|
+
const recipe$5 = defineSlotRecipe({
|
|
760
|
+
base: {
|
|
761
|
+
body: {
|
|
762
|
+
textStyle: "body.sm",
|
|
763
|
+
padding: dense("{spacing.inset.sm}")
|
|
764
|
+
},
|
|
765
|
+
closeTrigger: { flexShrink: "0" },
|
|
766
|
+
content: {
|
|
767
|
+
...motion("scale-fade.in", "scale-fade.out"),
|
|
768
|
+
focusVisibleRing: "outside",
|
|
769
|
+
background: "bg.panel",
|
|
770
|
+
borderColor: "transparent",
|
|
771
|
+
borderRadius: "l3",
|
|
772
|
+
borderStyle: "solid",
|
|
773
|
+
borderWidth: "hairline",
|
|
774
|
+
boxShadow: "lg",
|
|
775
|
+
color: "fg",
|
|
776
|
+
display: "flex",
|
|
777
|
+
flexDirection: "column",
|
|
778
|
+
focusRingColor: "colorPalette.focusRing",
|
|
779
|
+
position: "relative"
|
|
780
|
+
},
|
|
781
|
+
control: {
|
|
782
|
+
alignItems: "center",
|
|
783
|
+
display: "flex",
|
|
784
|
+
flexShrink: "0",
|
|
785
|
+
gap: dense("{spacing.gap.xs}")
|
|
786
|
+
},
|
|
787
|
+
dragTrigger: {
|
|
788
|
+
"&[data-disabled]": { cursor: "auto" },
|
|
789
|
+
"& > svg": {
|
|
790
|
+
boxSize: "icon.sm",
|
|
791
|
+
color: "fg.subtle",
|
|
792
|
+
flexShrink: "0"
|
|
793
|
+
},
|
|
794
|
+
[`.${CLASS$4}__header[data-dragging] > &`]: { cursor: "dragging" },
|
|
795
|
+
alignItems: "center",
|
|
796
|
+
alignSelf: "stretch",
|
|
797
|
+
cursor: "drag",
|
|
798
|
+
display: "flex",
|
|
799
|
+
flex: "1",
|
|
800
|
+
gap: dense("{spacing.gap.sm}"),
|
|
801
|
+
minInlineSize: "0",
|
|
802
|
+
touchAction: "none",
|
|
803
|
+
userSelect: "none"
|
|
804
|
+
},
|
|
805
|
+
header: {
|
|
806
|
+
alignItems: "center",
|
|
807
|
+
borderBlockEndColor: "border",
|
|
808
|
+
borderBlockEndStyle: "solid",
|
|
809
|
+
borderBlockEndWidth: "hairline",
|
|
810
|
+
display: "flex",
|
|
811
|
+
flexShrink: "0",
|
|
812
|
+
gap: dense("{spacing.gap.sm}"),
|
|
813
|
+
paddingBlock: dense("{spacing.inset.xs}"),
|
|
814
|
+
paddingInlineEnd: dense("{spacing.inset.xs}"),
|
|
815
|
+
paddingInlineStart: dense("{spacing.inset.sm}")
|
|
816
|
+
},
|
|
817
|
+
positioner: { zIndex: LAYERED },
|
|
818
|
+
resizeTrigger: {
|
|
819
|
+
"&:is([data-axis=e], [data-axis=w])": { inlineSize: STRIP$2 },
|
|
820
|
+
"&:is([data-axis=n], [data-axis=s])": { blockSize: STRIP$2 },
|
|
821
|
+
"&:is([data-axis=ne], [data-axis=nw], [data-axis=se], [data-axis=sw])": {
|
|
822
|
+
blockSize: STRIP$2,
|
|
823
|
+
inlineSize: STRIP$2
|
|
824
|
+
},
|
|
825
|
+
"&[data-disabled]": { display: "none" },
|
|
826
|
+
zIndex: "docked"
|
|
827
|
+
},
|
|
828
|
+
root: { display: "contents" },
|
|
829
|
+
scroller: {
|
|
830
|
+
"&[hidden]": { display: "none" },
|
|
831
|
+
flex: "1",
|
|
832
|
+
marginBlockEnd: STRIP$2,
|
|
833
|
+
marginInlineEnd: STRIP$2,
|
|
834
|
+
minBlockSize: "0",
|
|
835
|
+
[RING_OFFSET]: "calc({borderWidths.ring} * -1)"
|
|
836
|
+
},
|
|
837
|
+
stageTrigger: {
|
|
838
|
+
"&[hidden]": { display: "none" },
|
|
839
|
+
flexShrink: "0"
|
|
840
|
+
},
|
|
841
|
+
title: {
|
|
842
|
+
...truncate(),
|
|
843
|
+
textStyle: "label.md",
|
|
844
|
+
fontWeight: "semibold"
|
|
845
|
+
},
|
|
846
|
+
trigger: {
|
|
847
|
+
...interactive(),
|
|
848
|
+
alignItems: "center",
|
|
849
|
+
display: "inline-flex",
|
|
850
|
+
gap: dense("{spacing.gap.xs}")
|
|
851
|
+
}
|
|
852
|
+
},
|
|
853
|
+
className: CLASS$4,
|
|
854
|
+
jsx: [/^FloatingPanel(\.\w+)?$/u],
|
|
855
|
+
slots: [
|
|
856
|
+
"root",
|
|
857
|
+
"trigger",
|
|
858
|
+
"positioner",
|
|
859
|
+
"content",
|
|
860
|
+
"header",
|
|
861
|
+
"dragTrigger",
|
|
862
|
+
"title",
|
|
863
|
+
"control",
|
|
864
|
+
"stageTrigger",
|
|
865
|
+
"closeTrigger",
|
|
866
|
+
"scroller",
|
|
867
|
+
"body",
|
|
868
|
+
"resizeTrigger"
|
|
869
|
+
]
|
|
870
|
+
});
|
|
871
|
+
//#endregion
|
|
872
|
+
//#region src/page/metrics.ts
|
|
873
|
+
/**
|
|
874
|
+
* Computes the rules the page recipe writes at each of its sizes: the padding and gaps of the
|
|
875
|
+
* bands, the tabs in the navigation band, the panel the tab picker opens and the title's heading
|
|
876
|
+
* role.
|
|
877
|
+
*
|
|
878
|
+
* @remarks
|
|
879
|
+
* The module is separate from the recipe so the recipe is under the 300-line limit. It imports no
|
|
880
|
+
* React, because the theme plugin evaluates the recipe in Node.
|
|
881
|
+
*/
|
|
882
|
+
/**
|
|
883
|
+
* Class name of the page's recipe, which a selector across bands reads.
|
|
884
|
+
*
|
|
885
|
+
* @remarks
|
|
886
|
+
* The binding writes one class per band, such as `page__nav`, and no attribute that names the
|
|
887
|
+
* band. A selector for another band builds that class from this constant.
|
|
888
|
+
*/
|
|
889
|
+
const CLASS$3 = "page";
|
|
890
|
+
/**
|
|
891
|
+
* Class name of the tabs' recipe, whose root the navigation band shrinks to its tabs.
|
|
892
|
+
*/
|
|
893
|
+
const TABS = "tabs";
|
|
894
|
+
/**
|
|
895
|
+
* Class name of the command palette's recipe, whose root the palette panel renders flat.
|
|
896
|
+
*/
|
|
897
|
+
const COMMAND = "command";
|
|
898
|
+
/**
|
|
899
|
+
* Selects the strip of tabs in the navigation band: the tabs' root, or the `Page.Tabs` slot where
|
|
900
|
+
* the tabs' root contains the panels too and the band contains the list alone.
|
|
901
|
+
*/
|
|
902
|
+
const STRIP$1 = `:is(.${TABS}__root, .${CLASS$3}__tabs)`;
|
|
903
|
+
/**
|
|
904
|
+
* Selects the strip of tabs as a child of the navigation band.
|
|
905
|
+
*/
|
|
906
|
+
const TABS_IN_NAV = `& > ${STRIP$1}`;
|
|
907
|
+
/**
|
|
908
|
+
* Selects every tab of a strip in the navigation band.
|
|
909
|
+
*/
|
|
910
|
+
const TAB_IN_NAV = `& .${TABS}__trigger`;
|
|
911
|
+
/**
|
|
912
|
+
* Sizes the page offers, which a section in the page reads too.
|
|
913
|
+
*/
|
|
914
|
+
const STEPS = [
|
|
915
|
+
"sm",
|
|
916
|
+
"md",
|
|
917
|
+
"lg"
|
|
918
|
+
];
|
|
919
|
+
/**
|
|
920
|
+
* Selects a band followed by the navigation, which then has the hairline.
|
|
921
|
+
*/
|
|
922
|
+
const BEFORE_NAV = `&:has(+ .${CLASS$3}__nav)`;
|
|
923
|
+
/**
|
|
924
|
+
* Selects a navigation band with a strip of tabs, which is flush with the band's hairline.
|
|
925
|
+
*/
|
|
926
|
+
const WITH_TABS = `&:has(> ${STRIP$1})`;
|
|
927
|
+
/**
|
|
928
|
+
* Tab height and inline inset per page size: a medium or large page's tabs are 48px tall and inset
|
|
929
|
+
* by the `md` inset, a small page's 44px and the `sm` inset.
|
|
930
|
+
*/
|
|
931
|
+
const TABBED = {
|
|
932
|
+
lg: ["xl", "md"],
|
|
933
|
+
md: ["xl", "md"],
|
|
934
|
+
sm: ["lg", "sm"]
|
|
935
|
+
};
|
|
936
|
+
/**
|
|
937
|
+
* Returns the navigation band's rules for its tabs at a page size.
|
|
938
|
+
*
|
|
939
|
+
* @remarks
|
|
940
|
+
* The tabs are taller than a control of their text's size, so the band's other controls centre
|
|
941
|
+
* on them with room above and below. Each tab reads its inline inset from the control inset
|
|
942
|
+
* properties, and the strip starts one inset before the gutter, so the first tab's words start
|
|
943
|
+
* where the title starts. The recipe writes the rules in its size variant, because the tabs' own
|
|
944
|
+
* size variant sets the height in the same layer and these selectors outweigh it.
|
|
945
|
+
* @param size - The page's size.
|
|
946
|
+
*/
|
|
947
|
+
function tabbed(size) {
|
|
948
|
+
const [height, inset] = TABBED[size];
|
|
949
|
+
const room = dense(`{spacing.inset.${inset}}`);
|
|
950
|
+
return {
|
|
951
|
+
[TAB_IN_NAV]: {
|
|
952
|
+
[CONTROL_INSET_END]: room,
|
|
953
|
+
[CONTROL_INSET_START]: room,
|
|
954
|
+
height: dense(`{sizes.control.${height}}`)
|
|
955
|
+
},
|
|
956
|
+
[TABS_IN_NAV]: { marginInlineStart: `calc(${room} * -1)` }
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Styles the panel the tab picker opens as one box: the popover's edge and shadow around the
|
|
961
|
+
* command palette's rows, with neither the popover's padding nor the palette's own edge.
|
|
962
|
+
*
|
|
963
|
+
* @remarks
|
|
964
|
+
* The popover pads its content and the palette draws a raised panel, so without these rules the
|
|
965
|
+
* list renders as a box inside a box. The padding rule selects the machine's `data-state` too,
|
|
966
|
+
* because the popover's size variant sets the padding in the same layer.
|
|
967
|
+
*/
|
|
968
|
+
const FLAT_PALETTE = {
|
|
969
|
+
"&[data-state]": { padding: "0" },
|
|
970
|
+
[`& > .${COMMAND}__root`]: {
|
|
971
|
+
borderRadius: "0",
|
|
972
|
+
borderWidth: "0",
|
|
973
|
+
boxShadow: "none",
|
|
974
|
+
maxBlockSize: "inherit"
|
|
975
|
+
},
|
|
976
|
+
maxBlockSize: "min(var(--available-height), {sizes.60})"
|
|
977
|
+
};
|
|
978
|
+
/**
|
|
979
|
+
* Selects the title of a narrow page.
|
|
980
|
+
*/
|
|
981
|
+
const NARROW_TITLE = `.${CLASS$3}__root[data-narrow] > .${CLASS$3}__header > &`;
|
|
982
|
+
/**
|
|
983
|
+
* Heading role of the title at each size, one size larger than a section title's at that size, and
|
|
984
|
+
* one size smaller on a narrow page.
|
|
985
|
+
*
|
|
986
|
+
* @remarks
|
|
987
|
+
* The page title is the one heading above every section title on the page, so it reads a larger
|
|
988
|
+
* role than theirs. A narrow page gives the title's row less room, so the title reads one role
|
|
989
|
+
* smaller there.
|
|
990
|
+
*/
|
|
991
|
+
const TITLES = {
|
|
992
|
+
lg: {
|
|
993
|
+
textStyle: "heading.xl",
|
|
994
|
+
[NARROW_TITLE]: { textStyle: "heading.lg" }
|
|
995
|
+
},
|
|
996
|
+
md: {
|
|
997
|
+
textStyle: "heading.lg",
|
|
998
|
+
[NARROW_TITLE]: { textStyle: "heading.md" }
|
|
999
|
+
},
|
|
1000
|
+
sm: {
|
|
1001
|
+
textStyle: "heading.md",
|
|
1002
|
+
[NARROW_TITLE]: { textStyle: "heading.sm" }
|
|
1003
|
+
}
|
|
1004
|
+
};
|
|
1005
|
+
/**
|
|
1006
|
+
* Rules the recipe's `size` axis writes on each slot it sizes, at the page's three sizes.
|
|
1007
|
+
*/
|
|
1008
|
+
const SIZES = onSlots({
|
|
1009
|
+
actions: sizeVariants((size) => ({ gap: dense(`{spacing.gap.${size}}`) }), STEPS),
|
|
1010
|
+
aside: sizeVariants((size) => ({ paddingBlock: dense(`{spacing.inset.${size}}`) }), STEPS),
|
|
1011
|
+
banner: sizeVariants((size) => ({ paddingBlock: dense(`{spacing.inset.${size}}`) }), STEPS),
|
|
1012
|
+
body: sizeVariants((size) => ({ paddingBlock: dense(`{spacing.inset.${size}}`) }), STEPS),
|
|
1013
|
+
context: sizeVariants((size) => ({
|
|
1014
|
+
textStyle: `body.${below(size)}`,
|
|
1015
|
+
gap: dense(`{spacing.gap.${size}}`),
|
|
1016
|
+
marginBlockEnd: size === "lg" ? "{spacing.1}" : "{spacing.0.5}"
|
|
1017
|
+
}), STEPS),
|
|
1018
|
+
description: {
|
|
1019
|
+
lg: { textStyle: "body.md" },
|
|
1020
|
+
md: { textStyle: "body.md" },
|
|
1021
|
+
sm: { textStyle: "body.sm" }
|
|
1022
|
+
},
|
|
1023
|
+
footer: sizeVariants((size) => ({
|
|
1024
|
+
gap: dense(`{spacing.gap.${size}}`),
|
|
1025
|
+
paddingBlock: dense(`{spacing.inset.${size}}`)
|
|
1026
|
+
}), STEPS),
|
|
1027
|
+
header: sizeVariants((size) => ({
|
|
1028
|
+
[BEFORE_NAV]: { paddingBlockEnd: dense(`{spacing.gap.${size}}`) },
|
|
1029
|
+
paddingBlock: dense(`{spacing.inset.${size}}`)
|
|
1030
|
+
}), STEPS),
|
|
1031
|
+
meta: sizeVariants((size) => ({
|
|
1032
|
+
gap: dense(`{spacing.gap.${size}}`),
|
|
1033
|
+
marginInlineStart: dense(`{spacing.gap.${size}}`)
|
|
1034
|
+
}), STEPS),
|
|
1035
|
+
nav: sizeVariants((size) => ({
|
|
1036
|
+
...tabbed(size),
|
|
1037
|
+
paddingBlock: dense(`{spacing.gap.${size}}`),
|
|
1038
|
+
[WITH_TABS]: { paddingBlock: "0" }
|
|
1039
|
+
}), STEPS),
|
|
1040
|
+
palette: sizeVariants(() => FLAT_PALETTE, STEPS),
|
|
1041
|
+
sidebar: sizeVariants((size) => ({ paddingBlock: dense(`{spacing.inset.${size}}`) }), STEPS),
|
|
1042
|
+
title: TITLES,
|
|
1043
|
+
toolbar: sizeVariants((size) => ({
|
|
1044
|
+
gap: dense(`{spacing.gap.${size}}`),
|
|
1045
|
+
paddingBlock: dense(`{spacing.gap.${size}}`)
|
|
1046
|
+
}), STEPS),
|
|
1047
|
+
trail: sizeVariants((size) => ({
|
|
1048
|
+
textStyle: `body.${below(size)}`,
|
|
1049
|
+
gap: dense(`{spacing.gap.${below(size)}}`)
|
|
1050
|
+
}), STEPS)
|
|
1051
|
+
});
|
|
1052
|
+
//#endregion
|
|
1053
|
+
//#region src/page/recipe.ts
|
|
1054
|
+
/**
|
|
1055
|
+
* Recipe for a page: a column of bands, from a banner and a header to a body and a footer.
|
|
1056
|
+
*
|
|
1057
|
+
* @remarks
|
|
1058
|
+
* The root is a column at least as tall as its container, so the footer of a short page renders
|
|
1059
|
+
* at the container's end. Every band runs edge to edge, so a sticky band's fill and hairline span
|
|
1060
|
+
* the full width, and each band's content starts at the gutter. A page with a measure stops its
|
|
1061
|
+
* content there, at the start or centred as `align` sets. The header is a grid of three rows, so
|
|
1062
|
+
* the context, the leading mark, the title, the meta, the actions and the description are
|
|
1063
|
+
* siblings placed by area name, and a part left out takes no room. The title's column shrinks,
|
|
1064
|
+
* so its text wraps while the actions keep the end of its row. The root sets the gutter and the
|
|
1065
|
+
* measure as custom properties that every band reads. The page measures its own width and sets
|
|
1066
|
+
* `folded` below the `sm` breakpoint. From the `lg` breakpoint of the window, a page with an
|
|
1067
|
+
* aside becomes a grid with the aside beside the body, because a rail beside the text needs the
|
|
1068
|
+
* window's width. Below it the aside stacks under the body, or leaves the page with
|
|
1069
|
+
* `folds="hide"`. A page at least as wide as the `md` breakpoint, by its own measurement, becomes
|
|
1070
|
+
* a grid with its sidebar at the start of the body's row, and a narrower page renders the
|
|
1071
|
+
* sidebar's picker as a band above the body. A sticky aside keeps to the top of its row, under
|
|
1072
|
+
* the shell's sticky bars, and so does a sticky sidebar. The recipe has no `palette` axis,
|
|
1073
|
+
* because a page is layout on the page's surface, and no `effect` axis, because a page is not a
|
|
1074
|
+
* control.
|
|
1075
|
+
*/
|
|
1076
|
+
/**
|
|
1077
|
+
* Custom property the root sets to the room at the page's inline edges.
|
|
1078
|
+
*/
|
|
1079
|
+
const GUTTER = "--page-gutter";
|
|
1080
|
+
/**
|
|
1081
|
+
* Custom property the root sets to the width of the page's content.
|
|
1082
|
+
*/
|
|
1083
|
+
const MEASURE = "--page-measure";
|
|
1084
|
+
/**
|
|
1085
|
+
* Custom property a band reads as its start padding before the measure.
|
|
1086
|
+
*
|
|
1087
|
+
* @remarks
|
|
1088
|
+
* A band runs edge to edge, so its fill is full bleed, and its padding keeps the content in the
|
|
1089
|
+
* measure. The `align` axis sets this property and every band reads it, so the header, the body
|
|
1090
|
+
* and the footer share one column at either alignment.
|
|
1091
|
+
*/
|
|
1092
|
+
const LEAD = "--page-lead";
|
|
1093
|
+
/**
|
|
1094
|
+
* Styles every band: edge to edge, with padding that keeps the content in the measure.
|
|
1095
|
+
*/
|
|
1096
|
+
const BAND$1 = {
|
|
1097
|
+
flexShrink: "0",
|
|
1098
|
+
minInlineSize: "0",
|
|
1099
|
+
paddingInlineEnd: `max(var(${GUTTER}), calc(100% - var(${MEASURE}, 100%) - var(${LEAD}, var(${GUTTER}))))`,
|
|
1100
|
+
paddingInlineStart: `var(${LEAD}, var(${GUTTER}))`
|
|
1101
|
+
};
|
|
1102
|
+
/**
|
|
1103
|
+
* Custom property the shell sets to the height of its sticky bars, under which a sticky band stops.
|
|
1104
|
+
*/
|
|
1105
|
+
const SHELL_TOP = "--app-shell-sticky-top";
|
|
1106
|
+
/**
|
|
1107
|
+
* Custom property the root sets on each sticky band to the height of the sticky bands before it.
|
|
1108
|
+
*/
|
|
1109
|
+
const STICKY_OFFSET = "--page-sticky-offset";
|
|
1110
|
+
/**
|
|
1111
|
+
* Custom property the root sets on itself to the height of every band that sticks to the top.
|
|
1112
|
+
*/
|
|
1113
|
+
const STICKY_TOP = "--page-sticky-top";
|
|
1114
|
+
/**
|
|
1115
|
+
* Custom property a sticky aside or sidebar takes, set to the height of the area that scrolls the
|
|
1116
|
+
* page.
|
|
1117
|
+
*/
|
|
1118
|
+
const SCROLLPORT = "--page-scrollport";
|
|
1119
|
+
/**
|
|
1120
|
+
* Custom property the sidebar sets to the width a person resized it to.
|
|
1121
|
+
*/
|
|
1122
|
+
const SIDEBAR_SIZE = "--page-sidebar-size";
|
|
1123
|
+
/**
|
|
1124
|
+
* Room a focus ring takes outside an element: its offset and its width.
|
|
1125
|
+
*/
|
|
1126
|
+
const RING_ROOM = "calc(var(--focus-ring-offset, 0px) + var(--focus-ring-width, 0px))";
|
|
1127
|
+
/**
|
|
1128
|
+
* Styles a sticky band: at the top, under the shell's sticky bars and the page's sticky bands
|
|
1129
|
+
* before it, on the page's fill.
|
|
1130
|
+
*
|
|
1131
|
+
* @remarks
|
|
1132
|
+
* A sticky band takes the page's fill, because the page scrolls under it. Its inset reads the
|
|
1133
|
+
* shell's sticky height and the height of the sticky bands before it, because a sticky element
|
|
1134
|
+
* with an `auto` inset does not stick and two bands at one inset cover each other. Its z-index is
|
|
1135
|
+
* the `sticky` token the shell's bars use, so a band and a bar at one edge share one order.
|
|
1136
|
+
*/
|
|
1137
|
+
const STUCK = {
|
|
1138
|
+
background: "bg",
|
|
1139
|
+
insetBlockStart: `calc(var(${SHELL_TOP}, 0px) + var(${STICKY_OFFSET}, 0px))`,
|
|
1140
|
+
position: "sticky",
|
|
1141
|
+
zIndex: "sticky"
|
|
1142
|
+
};
|
|
1143
|
+
/**
|
|
1144
|
+
* Styles a column that sticks to the top of its row, under the shell's sticky bars and the page's
|
|
1145
|
+
* sticky bands, a gap below them.
|
|
1146
|
+
*/
|
|
1147
|
+
const STICKY_COLUMN = {
|
|
1148
|
+
alignSelf: "start",
|
|
1149
|
+
insetBlockStart: `calc(var(${SHELL_TOP}, 0px) + var(${STICKY_TOP}, 0px) + {spacing.gap.xl})`,
|
|
1150
|
+
position: "sticky"
|
|
1151
|
+
};
|
|
1152
|
+
/**
|
|
1153
|
+
* Caps a sticky column at the height of the area that scrolls the page, less the sticky bars and
|
|
1154
|
+
* bands above it and a gap at each end, as a flex column that its scroll area fills.
|
|
1155
|
+
*/
|
|
1156
|
+
const CAPPED_COLUMN = {
|
|
1157
|
+
display: "flex",
|
|
1158
|
+
flexDirection: "column",
|
|
1159
|
+
maxBlockSize: `calc(var(${SCROLLPORT}, 100dvh) - var(${SHELL_TOP}, 0px) - var(${STICKY_TOP}, 0px) - {spacing.gap.xl} * 2)`
|
|
1160
|
+
};
|
|
1161
|
+
/**
|
|
1162
|
+
* Styles a row that centres its items, wraps and shrinks.
|
|
1163
|
+
*/
|
|
1164
|
+
const ROW$1 = {
|
|
1165
|
+
alignItems: "center",
|
|
1166
|
+
display: "flex",
|
|
1167
|
+
flexWrap: "wrap",
|
|
1168
|
+
minInlineSize: "0"
|
|
1169
|
+
};
|
|
1170
|
+
/**
|
|
1171
|
+
* Selects a root with an aside, which lays its bands out as a grid from the `lg` breakpoint.
|
|
1172
|
+
*/
|
|
1173
|
+
const WITH_ASIDE = `&:has(> .${CLASS$3}__aside)`;
|
|
1174
|
+
/**
|
|
1175
|
+
* Selects a root with a sidebar beside its body, which lays its bands out as a grid.
|
|
1176
|
+
*/
|
|
1177
|
+
const WITH_SIDEBAR = `&:has(> .${CLASS$3}__sidebar[data-beside])`;
|
|
1178
|
+
/**
|
|
1179
|
+
* Selects a toolbar in the navigation band.
|
|
1180
|
+
*/
|
|
1181
|
+
const TOOLBAR_IN_NAV = `& > .toolbar__root`;
|
|
1182
|
+
/**
|
|
1183
|
+
* Selects a strip of tabs in the navigation band, by the orientation the tabs' own line look
|
|
1184
|
+
* selects too.
|
|
1185
|
+
*/
|
|
1186
|
+
const STRIP_IN_NAV = `& .${CLASS$3}__tabs[data-orientation]`;
|
|
1187
|
+
/**
|
|
1188
|
+
* Styles the grid of a page with an aside: every band across, and the body beside the aside.
|
|
1189
|
+
*
|
|
1190
|
+
* @remarks
|
|
1191
|
+
* The body's row takes the remaining height and every other row takes its content's height,
|
|
1192
|
+
* because the root is at least as tall as the shell's main region and a grid shares spare height
|
|
1193
|
+
* between its `auto` rows. The sidebar's row holds the picker a narrow page renders in the
|
|
1194
|
+
* sidebar's place, and takes no height without one.
|
|
1195
|
+
*/
|
|
1196
|
+
const BESIDE = {
|
|
1197
|
+
columnGap: dense("{spacing.gap.xl}"),
|
|
1198
|
+
display: "grid",
|
|
1199
|
+
gridTemplateAreas: "\"banner banner\" \"header header\" \"nav nav\" \"toolbar toolbar\" \"sidebar sidebar\" \"body aside\" \"footer footer\"",
|
|
1200
|
+
gridTemplateColumns: "minmax(0, 1fr) auto",
|
|
1201
|
+
gridTemplateRows: "auto auto auto auto auto 1fr auto"
|
|
1202
|
+
};
|
|
1203
|
+
/**
|
|
1204
|
+
* Styles the grid of a page with a sidebar beside its body: every band across, the sidebar at the
|
|
1205
|
+
* start, and an aside under the body.
|
|
1206
|
+
*/
|
|
1207
|
+
const SIDEBAR_BESIDE = {
|
|
1208
|
+
columnGap: dense("{spacing.gap.xl}"),
|
|
1209
|
+
display: "grid",
|
|
1210
|
+
gridTemplateAreas: "\"banner banner\" \"header header\" \"nav nav\" \"toolbar toolbar\" \"sidebar body\" \"sidebar aside\" \"footer footer\"",
|
|
1211
|
+
gridTemplateColumns: "auto minmax(0, 1fr)",
|
|
1212
|
+
gridTemplateRows: "auto auto auto auto 1fr auto auto"
|
|
1213
|
+
};
|
|
1214
|
+
/**
|
|
1215
|
+
* Styles the grid of a page with a sidebar beside its body and an aside beside it from the `lg`
|
|
1216
|
+
* breakpoint: the sidebar, the body and the aside in one row.
|
|
1217
|
+
*/
|
|
1218
|
+
const SIDEBAR_AND_ASIDE = {
|
|
1219
|
+
gridTemplateAreas: "\"banner banner banner\" \"header header header\" \"nav nav nav\" \"toolbar toolbar toolbar\" \"sidebar body aside\" \"footer footer footer\"",
|
|
1220
|
+
gridTemplateColumns: "auto minmax(0, 1fr) auto",
|
|
1221
|
+
gridTemplateRows: "auto auto auto auto 1fr auto"
|
|
1222
|
+
};
|
|
1223
|
+
/**
|
|
1224
|
+
* Defines the page recipe: a full-width page at size `md` by default.
|
|
1225
|
+
*/
|
|
1226
|
+
const recipe$4 = defineSlotRecipe({
|
|
1227
|
+
base: {
|
|
1228
|
+
action: {
|
|
1229
|
+
...FOLDING,
|
|
1230
|
+
flexShrink: "0"
|
|
1231
|
+
},
|
|
1232
|
+
actions: {
|
|
1233
|
+
...ROW$1,
|
|
1234
|
+
flexWrap: "nowrap",
|
|
1235
|
+
gridArea: "actions",
|
|
1236
|
+
justifySelf: "end",
|
|
1237
|
+
paddingInlineStart: dense("{spacing.inset.xl}")
|
|
1238
|
+
},
|
|
1239
|
+
/**
|
|
1240
|
+
* The aside, beside the body from the `lg` breakpoint and under it below.
|
|
1241
|
+
*
|
|
1242
|
+
* @remarks
|
|
1243
|
+
* Beside the body a sticky aside is a column at most as tall as the area that scrolls the
|
|
1244
|
+
* page, less the sticky bars and bands above it and a gap at each end. Its scroll area fills
|
|
1245
|
+
* the column, so the end of a long aside remains reachable while it sticks.
|
|
1246
|
+
*/
|
|
1247
|
+
aside: {
|
|
1248
|
+
"&[data-folds=hide]": { lgDown: { display: "none" } },
|
|
1249
|
+
"&[data-sticky]": {
|
|
1250
|
+
...STICKY_COLUMN,
|
|
1251
|
+
lg: CAPPED_COLUMN
|
|
1252
|
+
},
|
|
1253
|
+
gridArea: "aside",
|
|
1254
|
+
lg: { paddingInlineStart: "0" },
|
|
1255
|
+
minInlineSize: "0",
|
|
1256
|
+
paddingInline: `var(${GUTTER})`
|
|
1257
|
+
},
|
|
1258
|
+
/**
|
|
1259
|
+
* The padding of a sticky aside's scroll area, the room a focus ring takes at its edges.
|
|
1260
|
+
*/
|
|
1261
|
+
asideContent: { padding: RING_ROOM },
|
|
1262
|
+
/**
|
|
1263
|
+
* A sticky aside's scroll area, pulled out on every side by the room its content's padding
|
|
1264
|
+
* takes, so the content keeps its place.
|
|
1265
|
+
*/
|
|
1266
|
+
asideScroller: { margin: `calc(${RING_ROOM} * -1)` },
|
|
1267
|
+
banner: {
|
|
1268
|
+
...BAND$1,
|
|
1269
|
+
flexShrink: "0",
|
|
1270
|
+
gridArea: "banner"
|
|
1271
|
+
},
|
|
1272
|
+
body: {
|
|
1273
|
+
...BAND$1,
|
|
1274
|
+
display: "flex",
|
|
1275
|
+
flex: "1",
|
|
1276
|
+
flexDirection: "column",
|
|
1277
|
+
gridArea: "body",
|
|
1278
|
+
minBlockSize: "0"
|
|
1279
|
+
},
|
|
1280
|
+
context: {
|
|
1281
|
+
...ROW$1,
|
|
1282
|
+
gridArea: "context",
|
|
1283
|
+
minInlineSize: "0"
|
|
1284
|
+
},
|
|
1285
|
+
description: {
|
|
1286
|
+
color: "fg.subtle",
|
|
1287
|
+
gridArea: "description",
|
|
1288
|
+
maxInlineSize: "prose",
|
|
1289
|
+
minInlineSize: "0"
|
|
1290
|
+
},
|
|
1291
|
+
/**
|
|
1292
|
+
* Lays the footer out as a row that sticks to the bottom edge when told to.
|
|
1293
|
+
*
|
|
1294
|
+
* @remarks
|
|
1295
|
+
* A sticky footer resets the top inset the shared sticky rule sets.
|
|
1296
|
+
*/
|
|
1297
|
+
footer: {
|
|
1298
|
+
...BAND$1,
|
|
1299
|
+
...ROW$1,
|
|
1300
|
+
"&[data-sticky]": {
|
|
1301
|
+
...STUCK,
|
|
1302
|
+
insetBlockEnd: "0",
|
|
1303
|
+
insetBlockStart: "auto"
|
|
1304
|
+
},
|
|
1305
|
+
gridArea: "footer"
|
|
1306
|
+
},
|
|
1307
|
+
/**
|
|
1308
|
+
* Lays the header out in three rows: the context, the title's row and the description.
|
|
1309
|
+
*
|
|
1310
|
+
* @remarks
|
|
1311
|
+
* The rows have no gap. The context row keeps a small margin above the title's row, and the
|
|
1312
|
+
* line heights part the title from the description. The columns have no gap, because the
|
|
1313
|
+
* parts beside the title belong to its line. Every part names its area, because the grid
|
|
1314
|
+
* places a part without one in the first free cell.
|
|
1315
|
+
*/
|
|
1316
|
+
header: {
|
|
1317
|
+
...BAND$1,
|
|
1318
|
+
"&[data-sticky]": STUCK,
|
|
1319
|
+
alignItems: "center",
|
|
1320
|
+
display: "grid",
|
|
1321
|
+
gridArea: "header",
|
|
1322
|
+
gridTemplateAreas: "\"context context context context\" \"leading title meta actions\" \"description description description description\"",
|
|
1323
|
+
gridTemplateColumns: "auto auto minmax(0, 1fr) auto"
|
|
1324
|
+
},
|
|
1325
|
+
leading: {
|
|
1326
|
+
display: "flex",
|
|
1327
|
+
gridArea: "leading",
|
|
1328
|
+
marginInlineEnd: dense("{spacing.gap.lg}")
|
|
1329
|
+
},
|
|
1330
|
+
meta: {
|
|
1331
|
+
...ROW$1,
|
|
1332
|
+
gridArea: "meta"
|
|
1333
|
+
},
|
|
1334
|
+
nav: {
|
|
1335
|
+
...BAND$1,
|
|
1336
|
+
"&[data-sticky]": STUCK,
|
|
1337
|
+
alignItems: "center",
|
|
1338
|
+
display: "flex",
|
|
1339
|
+
flexWrap: "wrap",
|
|
1340
|
+
gap: dense("{spacing.gap.lg}"),
|
|
1341
|
+
gridArea: "nav",
|
|
1342
|
+
justifyContent: "space-between",
|
|
1343
|
+
[TABS_IN_NAV]: {
|
|
1344
|
+
flex: "0 1 auto",
|
|
1345
|
+
inlineSize: "auto",
|
|
1346
|
+
minInlineSize: "0"
|
|
1347
|
+
},
|
|
1348
|
+
[TOOLBAR_IN_NAV]: {
|
|
1349
|
+
flex: "0 1 auto",
|
|
1350
|
+
inlineSize: "auto"
|
|
1351
|
+
}
|
|
1352
|
+
},
|
|
1353
|
+
palette: {
|
|
1354
|
+
inlineSize: "var(--reference-width)",
|
|
1355
|
+
overflow: "clip"
|
|
1356
|
+
},
|
|
1357
|
+
picker: {
|
|
1358
|
+
"& > span": truncate(),
|
|
1359
|
+
flex: "1",
|
|
1360
|
+
justifyContent: "space-between",
|
|
1361
|
+
minInlineSize: "0"
|
|
1362
|
+
},
|
|
1363
|
+
root: {
|
|
1364
|
+
[`&[${RESIZING}]`]: {
|
|
1365
|
+
cursor: "resizeColumn",
|
|
1366
|
+
userSelect: "none"
|
|
1367
|
+
},
|
|
1368
|
+
display: "flex",
|
|
1369
|
+
flexDirection: "column",
|
|
1370
|
+
flexGrow: "1",
|
|
1371
|
+
inlineSize: "100%",
|
|
1372
|
+
minBlockSize: "100%",
|
|
1373
|
+
minInlineSize: "0",
|
|
1374
|
+
[WITH_ASIDE]: { lg: BESIDE },
|
|
1375
|
+
[WITH_SIDEBAR]: {
|
|
1376
|
+
...SIDEBAR_BESIDE,
|
|
1377
|
+
[WITH_ASIDE]: { lg: SIDEBAR_AND_ASIDE }
|
|
1378
|
+
}
|
|
1379
|
+
},
|
|
1380
|
+
/**
|
|
1381
|
+
* The sidebar: a column beside the body on a page as wide as the `md` breakpoint, and a band
|
|
1382
|
+
* with the picker on a narrower page.
|
|
1383
|
+
*
|
|
1384
|
+
* @remarks
|
|
1385
|
+
* Beside the body the column starts at the gutter, so the fills of its rows start where the
|
|
1386
|
+
* title starts. The column is as wide as the theme's sidebar unless a person resized it. A
|
|
1387
|
+
* sticky sidebar keeps to the top of its row and is at most as tall as the room under the
|
|
1388
|
+
* sticky bands, as a sticky aside is. The handle is centred on the column's end edge, in the
|
|
1389
|
+
* gap before the body.
|
|
1390
|
+
*/
|
|
1391
|
+
sidebar: {
|
|
1392
|
+
...BAND$1,
|
|
1393
|
+
"&[data-beside]": {
|
|
1394
|
+
"&[data-sticky]": {
|
|
1395
|
+
...STICKY_COLUMN,
|
|
1396
|
+
...CAPPED_COLUMN
|
|
1397
|
+
},
|
|
1398
|
+
inlineSize: `var(${SIDEBAR_SIZE}, {sizes.sidebar})`,
|
|
1399
|
+
paddingInlineEnd: "0",
|
|
1400
|
+
position: "relative"
|
|
1401
|
+
},
|
|
1402
|
+
gridArea: "sidebar"
|
|
1403
|
+
},
|
|
1404
|
+
/**
|
|
1405
|
+
* The padding of a sticky sidebar's scroll area, the room a focus ring takes at its edges.
|
|
1406
|
+
*/
|
|
1407
|
+
sidebarContent: { padding: RING_ROOM },
|
|
1408
|
+
/**
|
|
1409
|
+
* The strip on a resizable sidebar's end edge, centred on the edge.
|
|
1410
|
+
*/
|
|
1411
|
+
sidebarHandle: {
|
|
1412
|
+
...STRIP$3,
|
|
1413
|
+
insetBlock: "0",
|
|
1414
|
+
insetInlineEnd: "calc({sizes.6} / -2)",
|
|
1415
|
+
position: "absolute"
|
|
1416
|
+
},
|
|
1417
|
+
/**
|
|
1418
|
+
* A sticky sidebar's scroll area, pulled out on every side by the room its content's padding
|
|
1419
|
+
* takes, so the content keeps its place.
|
|
1420
|
+
*/
|
|
1421
|
+
sidebarScroller: { margin: `calc(${RING_ROOM} * -1)` },
|
|
1422
|
+
title: {
|
|
1423
|
+
gridArea: "title",
|
|
1424
|
+
minInlineSize: "0",
|
|
1425
|
+
overflowWrap: "anywhere"
|
|
1426
|
+
},
|
|
1427
|
+
toolbar: {
|
|
1428
|
+
...BAND$1,
|
|
1429
|
+
...ROW$1,
|
|
1430
|
+
"&[data-sticky]": STUCK,
|
|
1431
|
+
gridArea: "toolbar"
|
|
1432
|
+
},
|
|
1433
|
+
trail: {
|
|
1434
|
+
"& > svg": { _rtl: { transform: "scaleX(-1)" } },
|
|
1435
|
+
alignItems: "center",
|
|
1436
|
+
color: "fg.muted",
|
|
1437
|
+
display: "inline-flex",
|
|
1438
|
+
gridArea: "context",
|
|
1439
|
+
justifySelf: "start",
|
|
1440
|
+
minInlineSize: "0"
|
|
1441
|
+
}
|
|
1442
|
+
},
|
|
1443
|
+
className: CLASS$3,
|
|
1444
|
+
compoundVariants: [(
|
|
1445
|
+
/**
|
|
1446
|
+
* Moves the meta of a folded header onto its own row under the title.
|
|
1447
|
+
*
|
|
1448
|
+
* @remarks
|
|
1449
|
+
* The meta drops the start margin it has beside the title, so it starts where the title and
|
|
1450
|
+
* the description start, and keeps a small margin above and below. The actions keep the `sm`
|
|
1451
|
+
* inset from the title.
|
|
1452
|
+
*/
|
|
1453
|
+
{
|
|
1454
|
+
css: {
|
|
1455
|
+
actions: { paddingInlineStart: dense("{spacing.inset.sm}") },
|
|
1456
|
+
header: {
|
|
1457
|
+
gridTemplateAreas: "\"context context context\" \"leading title actions\" \"meta meta meta\" \"description description description\"",
|
|
1458
|
+
gridTemplateColumns: "auto minmax(0, 1fr) auto"
|
|
1459
|
+
},
|
|
1460
|
+
meta: {
|
|
1461
|
+
marginBlock: dense("{spacing.gap.sm}"),
|
|
1462
|
+
marginInlineStart: "0"
|
|
1463
|
+
}
|
|
1464
|
+
},
|
|
1465
|
+
folded: true,
|
|
1466
|
+
name: "stacked"
|
|
1467
|
+
})],
|
|
1468
|
+
defaultVariants: {
|
|
1469
|
+
align: "start",
|
|
1470
|
+
divided: true,
|
|
1471
|
+
gutter: "xl",
|
|
1472
|
+
measure: "full",
|
|
1473
|
+
size: "md"
|
|
1474
|
+
},
|
|
1475
|
+
jsx: [/^Page(\.\w+)?$/u],
|
|
1476
|
+
slots: [
|
|
1477
|
+
"root",
|
|
1478
|
+
"banner",
|
|
1479
|
+
"header",
|
|
1480
|
+
"context",
|
|
1481
|
+
"leading",
|
|
1482
|
+
"title",
|
|
1483
|
+
"meta",
|
|
1484
|
+
"description",
|
|
1485
|
+
"actions",
|
|
1486
|
+
"action",
|
|
1487
|
+
"nav",
|
|
1488
|
+
"tabs",
|
|
1489
|
+
"picker",
|
|
1490
|
+
"palette",
|
|
1491
|
+
"toolbar",
|
|
1492
|
+
"sidebar",
|
|
1493
|
+
"sidebarScroller",
|
|
1494
|
+
"sidebarContent",
|
|
1495
|
+
"sidebarHandle",
|
|
1496
|
+
"body",
|
|
1497
|
+
"aside",
|
|
1498
|
+
"asideScroller",
|
|
1499
|
+
"asideContent",
|
|
1500
|
+
"footer",
|
|
1501
|
+
"trail"
|
|
1502
|
+
],
|
|
1503
|
+
variants: {
|
|
1504
|
+
/**
|
|
1505
|
+
* Position of the content when the measure is narrower than the page.
|
|
1506
|
+
*
|
|
1507
|
+
* @remarks
|
|
1508
|
+
* The axis sets the start padding of every band. The root runs edge to edge, so a band's fill
|
|
1509
|
+
* is full bleed, and automatic margins on a full-width root would move nothing.
|
|
1510
|
+
*/
|
|
1511
|
+
align: {
|
|
1512
|
+
start: { root: { [LEAD]: `var(${GUTTER})` } },
|
|
1513
|
+
center: { root: { [LEAD]: `max(var(${GUTTER}), calc((100% - var(${MEASURE}, 100%)) / 2))` } }
|
|
1514
|
+
},
|
|
1515
|
+
/**
|
|
1516
|
+
* Whether a hairline separates the bands.
|
|
1517
|
+
*
|
|
1518
|
+
* @remarks
|
|
1519
|
+
* A header followed by the navigation leaves the hairline to the navigation, and a strip of
|
|
1520
|
+
* tabs in the navigation drops its own line, so the strip has one line under it and none
|
|
1521
|
+
* above. The strip's rule outweighs the tabs' own line look, whose selector names the
|
|
1522
|
+
* orientation, because both are in the variants layer.
|
|
1523
|
+
*/
|
|
1524
|
+
divided: { true: {
|
|
1525
|
+
footer: {
|
|
1526
|
+
...divider("horizontal"),
|
|
1527
|
+
borderBlockStartWidth: "hairline"
|
|
1528
|
+
},
|
|
1529
|
+
header: {
|
|
1530
|
+
...divider("horizontal"),
|
|
1531
|
+
[BEFORE_NAV]: { borderBlockEndWidth: "0" },
|
|
1532
|
+
borderBlockEndWidth: "hairline"
|
|
1533
|
+
},
|
|
1534
|
+
nav: {
|
|
1535
|
+
...divider("horizontal"),
|
|
1536
|
+
borderBlockEndWidth: "hairline",
|
|
1537
|
+
[STRIP_IN_NAV]: { borderBlockEndWidth: "0" }
|
|
1538
|
+
},
|
|
1539
|
+
toolbar: {
|
|
1540
|
+
...divider("horizontal"),
|
|
1541
|
+
borderBlockEndWidth: "hairline"
|
|
1542
|
+
}
|
|
1543
|
+
} },
|
|
1544
|
+
/**
|
|
1545
|
+
* Room at the page's inline edges, which every band reads.
|
|
1546
|
+
*
|
|
1547
|
+
* @remarks
|
|
1548
|
+
* The default is the `xl` inset. A folded page sets the `md` inset.
|
|
1549
|
+
*/
|
|
1550
|
+
gutter: onSlots({ root: sizeVariants((size) => ({ [GUTTER]: `{spacing.inset.${size}}` })) }),
|
|
1551
|
+
/**
|
|
1552
|
+
* Width of the page's content.
|
|
1553
|
+
*
|
|
1554
|
+
* @remarks
|
|
1555
|
+
* The axis is not named `width`, because `width` is a style prop of a styled element.
|
|
1556
|
+
* `narrow` and `wide` read the theme's page sizes.
|
|
1557
|
+
*/
|
|
1558
|
+
measure: {
|
|
1559
|
+
full: { root: { [MEASURE]: "100%" } },
|
|
1560
|
+
narrow: { root: { [MEASURE]: "{sizes.page.narrow}" } },
|
|
1561
|
+
wide: { root: { [MEASURE]: "{sizes.page.wide}" } }
|
|
1562
|
+
},
|
|
1563
|
+
/**
|
|
1564
|
+
* Whether the page is narrower than the `sm` breakpoint, which the page sets from its own
|
|
1565
|
+
* width.
|
|
1566
|
+
*
|
|
1567
|
+
* @remarks
|
|
1568
|
+
* A folded page has the `md` gutter. The axis is not named `narrow`, because `measure` has a
|
|
1569
|
+
* `narrow` value and two values of one name compile to one class.
|
|
1570
|
+
*/
|
|
1571
|
+
folded: { true: { root: { [GUTTER]: "{spacing.inset.md}" } } },
|
|
1572
|
+
/**
|
|
1573
|
+
* Size of the title, the description, the actions and the bands' padding.
|
|
1574
|
+
*
|
|
1575
|
+
* @remarks
|
|
1576
|
+
* The page offers three sizes, because larger steps belong to a display heading. A section in
|
|
1577
|
+
* the page reads this size too.
|
|
1578
|
+
*/
|
|
1579
|
+
size: SIZES
|
|
1580
|
+
}
|
|
1581
|
+
});
|
|
1582
|
+
//#endregion
|
|
1583
|
+
//#region src/section/recipe.ts
|
|
1584
|
+
/**
|
|
1585
|
+
* Recipe for a section of a page: a header over a body and a footer, plain or on a card.
|
|
1586
|
+
*
|
|
1587
|
+
* @remarks
|
|
1588
|
+
* The header is a grid of two rows, so the title, the actions and the description are siblings
|
|
1589
|
+
* placed by area name. The title's column shrinks, and the actions keep the end of the title's
|
|
1590
|
+
* row at every width. The root sets four custom properties per size: the gap between the bands,
|
|
1591
|
+
* the gap after the section before it, the card's inner room and the footer's block padding. A
|
|
1592
|
+
* card's bands pad themselves from them, so a body with `data-bleed` drops its padding while the
|
|
1593
|
+
* other bands keep theirs. A bleeding body overlaps the footer by one hairline, so a table's rule
|
|
1594
|
+
* under its last row and the footer's rule render as one line. The room is the inset of the
|
|
1595
|
+
* section's size, the inline padding of a table cell at that size, so a table in a bleeding body
|
|
1596
|
+
* starts its first column under the title. The description stops at the reading measure and
|
|
1597
|
+
* reads the body role one size smaller than the section. The title reads the heading role one
|
|
1598
|
+
* size smaller, so a section reads as part of its page. `annotated` moves the header into a
|
|
1599
|
+
* column beside the body and folds it back over the body while the root is `data-narrow`, which
|
|
1600
|
+
* the component measures. An annotated card raises the body and the footer as one card and leaves
|
|
1601
|
+
* the header on the page. A section scrolled to by its id stops one gap under the shell's sticky
|
|
1602
|
+
* bars and the page's sticky bands. The recipe has no `palette` axis, because a section is layout
|
|
1603
|
+
* on the page's surface and its content sets its own palettes, and no `effect` axis, because a
|
|
1604
|
+
* section is not a control.
|
|
1605
|
+
*/
|
|
1606
|
+
/**
|
|
1607
|
+
* Class name of the recipe, which a selector across parts reads.
|
|
1608
|
+
*/
|
|
1609
|
+
const CLASS$2 = "section";
|
|
1610
|
+
/**
|
|
1611
|
+
* Selects a section that follows another section.
|
|
1612
|
+
*/
|
|
1613
|
+
const AFTER = `.${CLASS$2}__root + &`;
|
|
1614
|
+
/**
|
|
1615
|
+
* Selects a band of a narrow section.
|
|
1616
|
+
*/
|
|
1617
|
+
const NARROW = `.${CLASS$2}__root[data-narrow] > &`;
|
|
1618
|
+
/**
|
|
1619
|
+
* Selects the actions of a narrow section.
|
|
1620
|
+
*/
|
|
1621
|
+
const NARROW_ACTIONS = `.${CLASS$2}__root[data-narrow] > .${CLASS$2}__header > &`;
|
|
1622
|
+
/**
|
|
1623
|
+
* Selects a band followed by the footer.
|
|
1624
|
+
*/
|
|
1625
|
+
const BEFORE_FOOTER = `&:has(+ .${CLASS$2}__footer)`;
|
|
1626
|
+
/**
|
|
1627
|
+
* Custom property the root sets to the card's inner room, which every band's padding reads.
|
|
1628
|
+
*/
|
|
1629
|
+
const ROOM = "--section-room";
|
|
1630
|
+
/**
|
|
1631
|
+
* Custom property the root sets to the gap between its bands.
|
|
1632
|
+
*/
|
|
1633
|
+
const BANDS = "--section-bands";
|
|
1634
|
+
/**
|
|
1635
|
+
* Custom property the root sets to the gap after the section before it.
|
|
1636
|
+
*/
|
|
1637
|
+
const APART = "--section-apart";
|
|
1638
|
+
/**
|
|
1639
|
+
* Custom property the root sets to the block padding of a card's footer.
|
|
1640
|
+
*/
|
|
1641
|
+
const FOOTING = "--section-footing";
|
|
1642
|
+
/**
|
|
1643
|
+
* Maps each size to the gap two sizes larger, between the bands.
|
|
1644
|
+
*/
|
|
1645
|
+
const AIRED = {
|
|
1646
|
+
lg: "2xl",
|
|
1647
|
+
md: "xl",
|
|
1648
|
+
sm: "lg"
|
|
1649
|
+
};
|
|
1650
|
+
/**
|
|
1651
|
+
* Maps each size to the gap three sizes larger, after the section before.
|
|
1652
|
+
*/
|
|
1653
|
+
const SPACED = {
|
|
1654
|
+
lg: "3xl",
|
|
1655
|
+
md: "2xl",
|
|
1656
|
+
sm: "xl"
|
|
1657
|
+
};
|
|
1658
|
+
/**
|
|
1659
|
+
* Styles a row that centres its items, wraps and shrinks.
|
|
1660
|
+
*/
|
|
1661
|
+
const ROW = {
|
|
1662
|
+
alignItems: "center",
|
|
1663
|
+
display: "flex",
|
|
1664
|
+
flexWrap: "wrap",
|
|
1665
|
+
minInlineSize: "0"
|
|
1666
|
+
};
|
|
1667
|
+
/**
|
|
1668
|
+
* Defines the section recipe: a plain section at size `md` by default.
|
|
1669
|
+
*/
|
|
1670
|
+
const recipe$3 = defineSlotRecipe({
|
|
1671
|
+
base: {
|
|
1672
|
+
action: {
|
|
1673
|
+
...FOLDING,
|
|
1674
|
+
flexShrink: "0"
|
|
1675
|
+
},
|
|
1676
|
+
actions: {
|
|
1677
|
+
...ROW,
|
|
1678
|
+
flexWrap: "nowrap",
|
|
1679
|
+
gridArea: "actions",
|
|
1680
|
+
justifySelf: "end",
|
|
1681
|
+
[NARROW_ACTIONS]: { paddingInlineStart: dense("{spacing.inset.sm}") },
|
|
1682
|
+
paddingInlineStart: dense("{spacing.inset.md}")
|
|
1683
|
+
},
|
|
1684
|
+
body: { minInlineSize: "0" },
|
|
1685
|
+
description: {
|
|
1686
|
+
color: "fg.subtle",
|
|
1687
|
+
gridArea: "description",
|
|
1688
|
+
maxInlineSize: "prose",
|
|
1689
|
+
minInlineSize: "0"
|
|
1690
|
+
},
|
|
1691
|
+
footer: {
|
|
1692
|
+
...ROW,
|
|
1693
|
+
"& > :last-child:not(:first-child)": { marginInlineStart: "auto" },
|
|
1694
|
+
justifyContent: "space-between"
|
|
1695
|
+
},
|
|
1696
|
+
header: {
|
|
1697
|
+
alignItems: "center",
|
|
1698
|
+
display: "grid",
|
|
1699
|
+
gridTemplateAreas: "\"title actions\" \"description description\"",
|
|
1700
|
+
gridTemplateColumns: "minmax(0, 1fr) auto",
|
|
1701
|
+
minInlineSize: "0"
|
|
1702
|
+
},
|
|
1703
|
+
root: {
|
|
1704
|
+
display: "flex",
|
|
1705
|
+
flexDirection: "column",
|
|
1706
|
+
gap: `var(${BANDS})`,
|
|
1707
|
+
minInlineSize: "0",
|
|
1708
|
+
scrollMarginBlockStart: "calc(var(--app-shell-sticky-top, 0px) + var(--page-sticky-top, 0px) + {spacing.gap.lg})"
|
|
1709
|
+
},
|
|
1710
|
+
title: {
|
|
1711
|
+
gridArea: "title",
|
|
1712
|
+
minInlineSize: "0",
|
|
1713
|
+
overflowWrap: "anywhere"
|
|
1714
|
+
}
|
|
1715
|
+
},
|
|
1716
|
+
className: CLASS$2,
|
|
1717
|
+
compoundVariants: [
|
|
1718
|
+
{
|
|
1719
|
+
annotated: true,
|
|
1720
|
+
css: {
|
|
1721
|
+
actions: {
|
|
1722
|
+
justifySelf: "start",
|
|
1723
|
+
marginBlockStart: dense("{spacing.gap.md}"),
|
|
1724
|
+
paddingInlineStart: "0"
|
|
1725
|
+
},
|
|
1726
|
+
body: { gridArea: "body" },
|
|
1727
|
+
footer: { gridArea: "footer" },
|
|
1728
|
+
header: {
|
|
1729
|
+
alignItems: "flex-start",
|
|
1730
|
+
display: "flex",
|
|
1731
|
+
flexDirection: "column",
|
|
1732
|
+
gridArea: "header"
|
|
1733
|
+
},
|
|
1734
|
+
root: {
|
|
1735
|
+
"&[data-narrow]": {
|
|
1736
|
+
gridTemplateAreas: "\"header\" \"body\" \"footer\"",
|
|
1737
|
+
gridTemplateColumns: "minmax(0, 1fr)",
|
|
1738
|
+
gridTemplateRows: "none"
|
|
1739
|
+
},
|
|
1740
|
+
alignItems: "start",
|
|
1741
|
+
display: "grid",
|
|
1742
|
+
gridTemplateAreas: "\"header body\" \"header footer\"",
|
|
1743
|
+
gridTemplateColumns: "minmax({sizes.xs}, 1fr) minmax(0, 2fr)",
|
|
1744
|
+
gridTemplateRows: "auto 1fr"
|
|
1745
|
+
}
|
|
1746
|
+
},
|
|
1747
|
+
name: "aside"
|
|
1748
|
+
},
|
|
1749
|
+
{
|
|
1750
|
+
css: {
|
|
1751
|
+
body: {
|
|
1752
|
+
"&:first-child": { paddingBlockStart: `var(${ROOM})` },
|
|
1753
|
+
"&[data-bleed]": {
|
|
1754
|
+
"&:first-child": { borderBlockStartWidth: "0" },
|
|
1755
|
+
[BEFORE_FOOTER]: { marginBlockEnd: "calc({borderWidths.hairline} * -1)" },
|
|
1756
|
+
borderBlockStartWidth: "hairline",
|
|
1757
|
+
borderColor: "border",
|
|
1758
|
+
padding: "0"
|
|
1759
|
+
},
|
|
1760
|
+
padding: `var(${ROOM})`,
|
|
1761
|
+
paddingBlockStart: "0"
|
|
1762
|
+
},
|
|
1763
|
+
footer: {
|
|
1764
|
+
borderBlockStartWidth: "hairline",
|
|
1765
|
+
borderColor: "border",
|
|
1766
|
+
paddingBlock: `var(${FOOTING})`,
|
|
1767
|
+
paddingInline: `var(${ROOM})`
|
|
1768
|
+
},
|
|
1769
|
+
header: {
|
|
1770
|
+
"&:last-child": { paddingBlockEnd: `var(${ROOM})` },
|
|
1771
|
+
padding: `var(${ROOM})`,
|
|
1772
|
+
paddingBlockEnd: `var(${BANDS})`
|
|
1773
|
+
},
|
|
1774
|
+
root: {
|
|
1775
|
+
...surface(),
|
|
1776
|
+
gap: "0"
|
|
1777
|
+
}
|
|
1778
|
+
},
|
|
1779
|
+
name: "raised",
|
|
1780
|
+
variant: "surface"
|
|
1781
|
+
},
|
|
1782
|
+
{
|
|
1783
|
+
annotated: true,
|
|
1784
|
+
css: {
|
|
1785
|
+
body: {
|
|
1786
|
+
...surface(),
|
|
1787
|
+
[BEFORE_FOOTER]: {
|
|
1788
|
+
borderBlockEndWidth: "0",
|
|
1789
|
+
borderEndEndRadius: "0",
|
|
1790
|
+
borderEndStartRadius: "0"
|
|
1791
|
+
},
|
|
1792
|
+
overflow: "clip",
|
|
1793
|
+
paddingBlockStart: `var(${ROOM})`
|
|
1794
|
+
},
|
|
1795
|
+
footer: {
|
|
1796
|
+
...surface(),
|
|
1797
|
+
[`.${CLASS$2}__body + &`]: {
|
|
1798
|
+
borderStartEndRadius: "0",
|
|
1799
|
+
borderStartStartRadius: "0"
|
|
1800
|
+
}
|
|
1801
|
+
},
|
|
1802
|
+
header: {
|
|
1803
|
+
[NARROW]: { marginBlockEnd: `var(${BANDS})` },
|
|
1804
|
+
padding: "0"
|
|
1805
|
+
},
|
|
1806
|
+
root: {
|
|
1807
|
+
background: "none",
|
|
1808
|
+
borderWidth: "0",
|
|
1809
|
+
boxShadow: "none",
|
|
1810
|
+
overflow: "visible",
|
|
1811
|
+
rowGap: "0"
|
|
1812
|
+
}
|
|
1813
|
+
},
|
|
1814
|
+
name: "beside",
|
|
1815
|
+
variant: "surface"
|
|
1816
|
+
}
|
|
1817
|
+
],
|
|
1818
|
+
defaultVariants: {
|
|
1819
|
+
size: "md",
|
|
1820
|
+
variant: "plain"
|
|
1821
|
+
},
|
|
1822
|
+
jsx: [/^Section(\.\w+)?$/u],
|
|
1823
|
+
slots: [
|
|
1824
|
+
"root",
|
|
1825
|
+
"header",
|
|
1826
|
+
"title",
|
|
1827
|
+
"description",
|
|
1828
|
+
"actions",
|
|
1829
|
+
"action",
|
|
1830
|
+
"body",
|
|
1831
|
+
"footer"
|
|
1832
|
+
],
|
|
1833
|
+
variants: {
|
|
1834
|
+
/**
|
|
1835
|
+
* Whether the header renders in a column beside the body.
|
|
1836
|
+
*/
|
|
1837
|
+
annotated: { true: { root: { columnGap: dense("{spacing.gap.3xl}") } } },
|
|
1838
|
+
/**
|
|
1839
|
+
* Size of the title, the description, the gaps and the card's inner room.
|
|
1840
|
+
*/
|
|
1841
|
+
size: onSlots({
|
|
1842
|
+
actions: sizeVariants((size) => ({ gap: dense(`{spacing.gap.${size}}`) }), [
|
|
1843
|
+
"sm",
|
|
1844
|
+
"md",
|
|
1845
|
+
"lg"
|
|
1846
|
+
]),
|
|
1847
|
+
description: sizeVariants((size) => ({
|
|
1848
|
+
textStyle: `body.${below(size)}`,
|
|
1849
|
+
marginBlockStart: size === "lg" ? "{spacing.1}" : "{spacing.0.5}"
|
|
1850
|
+
}), [
|
|
1851
|
+
"sm",
|
|
1852
|
+
"md",
|
|
1853
|
+
"lg"
|
|
1854
|
+
]),
|
|
1855
|
+
footer: sizeVariants((size) => ({ gap: dense(`{spacing.gap.${size}}`) }), [
|
|
1856
|
+
"sm",
|
|
1857
|
+
"md",
|
|
1858
|
+
"lg"
|
|
1859
|
+
]),
|
|
1860
|
+
root: sizeVariants((size) => ({
|
|
1861
|
+
[APART]: dense(`{spacing.gap.${SPACED[size]}}`),
|
|
1862
|
+
[BANDS]: dense(`{spacing.gap.${AIRED[size]}}`),
|
|
1863
|
+
[FOOTING]: dense(`{spacing.inset.${below(size)}}`),
|
|
1864
|
+
[ROOM]: dense(`{spacing.inset.${size}}`)
|
|
1865
|
+
}), [
|
|
1866
|
+
"sm",
|
|
1867
|
+
"md",
|
|
1868
|
+
"lg"
|
|
1869
|
+
]),
|
|
1870
|
+
title: sizeVariants((size) => ({ textStyle: `heading.${below(size)}` }), [
|
|
1871
|
+
"sm",
|
|
1872
|
+
"md",
|
|
1873
|
+
"lg"
|
|
1874
|
+
])
|
|
1875
|
+
}),
|
|
1876
|
+
/**
|
|
1877
|
+
* Look of the section: plain on the page, or raised as a card.
|
|
1878
|
+
*
|
|
1879
|
+
* @remarks
|
|
1880
|
+
* Both looks keep the size's gap after a section before them. A plain section after another
|
|
1881
|
+
* plain section renders a hairline in the middle of that gap. `surface` raises the section
|
|
1882
|
+
* as a card and clips its content to the card's corners, so a body with `data-bleed` keeps
|
|
1883
|
+
* them.
|
|
1884
|
+
*/
|
|
1885
|
+
variant: {
|
|
1886
|
+
surface: { root: {
|
|
1887
|
+
[AFTER]: { marginBlockStart: `var(${APART})` },
|
|
1888
|
+
overflow: "clip"
|
|
1889
|
+
} },
|
|
1890
|
+
plain: { root: {
|
|
1891
|
+
"& + &": {
|
|
1892
|
+
borderBlockStartWidth: "hairline",
|
|
1893
|
+
borderColor: "border",
|
|
1894
|
+
paddingBlockStart: `var(${APART})`
|
|
1895
|
+
},
|
|
1896
|
+
[AFTER]: { marginBlockStart: `var(${APART})` }
|
|
1897
|
+
} }
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
});
|
|
1901
|
+
//#endregion
|
|
1902
|
+
//#region src/sidebar/recipe.ts
|
|
1903
|
+
/**
|
|
1904
|
+
* Declares the sidebar's slot recipe, which lays out a fixed header, scrolling content and a fixed
|
|
1905
|
+
* footer in a column.
|
|
1906
|
+
*
|
|
1907
|
+
* @remarks
|
|
1908
|
+
* The recipe has twelve slots. The content scrolls in the primitives package's scroll area, whose
|
|
1909
|
+
* root is the `scroller` and fills the column, and the root does not scroll, so the header and
|
|
1910
|
+
* the footer remain in place however long the list of destinations is. The header and the footer
|
|
1911
|
+
* are columns padded like the content, so a search, a nav block or a switcher in a band is as
|
|
1912
|
+
* wide as the rows' fills, and a line of words in a band starts where the rows' icons start. A
|
|
1913
|
+
* nav block is a grid of two columns: the label and the headings take the first, the block's
|
|
1914
|
+
* control takes the second, and every other child spans both. A block's label is as tall as a
|
|
1915
|
+
* row, small, uppercase and in `fg.subtle`. A block whose rows a query matches none of is hidden,
|
|
1916
|
+
* unless it renders an empty message. On a rail the labels, the headings and the lines of words
|
|
1917
|
+
* in the bands are hidden visually and kept for screen readers, the empty message and the block's
|
|
1918
|
+
* control are removed, and the search centres its button. The app shell sets the sidebar's width,
|
|
1919
|
+
* and the root reads the shell's panel to render the rail. The recipe has no `palette` and no
|
|
1920
|
+
* `effect` axis: its looks are neutral grounds, and the navigation list inside it offers its own
|
|
1921
|
+
* palette and effect for the current row.
|
|
1922
|
+
*/
|
|
1923
|
+
/**
|
|
1924
|
+
* Selects a part inside a sidebar collapsed to a rail.
|
|
1925
|
+
*/
|
|
1926
|
+
const ICONIC = "[data-iconic] &";
|
|
1927
|
+
/**
|
|
1928
|
+
* Selects the children of a band that fold to an icon by themselves: a search, a nav block, a
|
|
1929
|
+
* navigation list and a switcher.
|
|
1930
|
+
*/
|
|
1931
|
+
const FOLDS = ".sidebar__search, .sidebar__nav, .nav-list__root, .switcher__root";
|
|
1932
|
+
/**
|
|
1933
|
+
* Selects the children of a band a caller writes around its words and marks: every child that does
|
|
1934
|
+
* not fold by itself.
|
|
1935
|
+
*/
|
|
1936
|
+
const LINES = `& > :not(${FOLDS})`;
|
|
1937
|
+
/**
|
|
1938
|
+
* Selects the lines of a band that have no icon form: every line except a bare `svg`.
|
|
1939
|
+
*/
|
|
1940
|
+
const WORDS = `& > :not(svg, ${FOLDS})`;
|
|
1941
|
+
/**
|
|
1942
|
+
* Styles the header and the footer as a column that keeps its height, and hides its words visually
|
|
1943
|
+
* on a rail.
|
|
1944
|
+
*
|
|
1945
|
+
* @remarks
|
|
1946
|
+
* On a rail only a bare `svg` and the parts that fold by themselves remain visible, so the rail
|
|
1947
|
+
* is as wide as its icons. The words remain in the accessibility tree, because the header's words
|
|
1948
|
+
* identify the workspace and the footer's words identify the signed-in person.
|
|
1949
|
+
*/
|
|
1950
|
+
const BANDED = {
|
|
1951
|
+
display: "flex",
|
|
1952
|
+
flexDirection: "column",
|
|
1953
|
+
flexShrink: "0",
|
|
1954
|
+
[ICONIC]: {
|
|
1955
|
+
alignItems: "center",
|
|
1956
|
+
[WORDS]: { srOnly: true }
|
|
1957
|
+
},
|
|
1958
|
+
minInlineSize: "0"
|
|
1959
|
+
};
|
|
1960
|
+
/**
|
|
1961
|
+
* Maps each sidebar size to the height of a navigation list row at the same size: 24px, 32px and
|
|
1962
|
+
* 40px at the foundation's metrics.
|
|
1963
|
+
*/
|
|
1964
|
+
const ROWS = {
|
|
1965
|
+
lg: "control.md",
|
|
1966
|
+
md: "control.xs",
|
|
1967
|
+
sm: "tag.md"
|
|
1968
|
+
};
|
|
1969
|
+
/**
|
|
1970
|
+
* Maps each sidebar size to the gap between the content's blocks, two gap sizes larger than the
|
|
1971
|
+
* content's padding: 12, 16 and 24px at the foundation's metrics.
|
|
1972
|
+
*/
|
|
1973
|
+
const BLOCKS = {
|
|
1974
|
+
lg: "2xl",
|
|
1975
|
+
md: "xl",
|
|
1976
|
+
sm: "lg"
|
|
1977
|
+
};
|
|
1978
|
+
/**
|
|
1979
|
+
* Returns the inline inset of a navigation list row at the sidebar's size.
|
|
1980
|
+
*
|
|
1981
|
+
* @remarks
|
|
1982
|
+
* The navigation list insets a row by `spacing.inset` two sizes smaller than the row. The
|
|
1983
|
+
* labels, the headings, the block's control and the lines of words in the bands use the same
|
|
1984
|
+
* inset, so their text and icons start and end where the rows' do: 8, 8 and 12px at `sm`, `md`
|
|
1985
|
+
* and `lg`.
|
|
1986
|
+
* @param size - The sidebar's size.
|
|
1987
|
+
* @returns The inset, multiplied by the density.
|
|
1988
|
+
*/
|
|
1989
|
+
function marked(size) {
|
|
1990
|
+
return dense(`{spacing.inset.${below(below(size))}}`);
|
|
1991
|
+
}
|
|
1992
|
+
/**
|
|
1993
|
+
* Returns the side of the block control's square: the tag size one size smaller than the sidebar,
|
|
1994
|
+
* and never under `sizes.6`.
|
|
1995
|
+
*
|
|
1996
|
+
* @remarks
|
|
1997
|
+
* The navigation list sizes the control and the count at the end of a row with the same
|
|
1998
|
+
* expression, so the block's control is in the rows' end column: 24px at every size.
|
|
1999
|
+
* @param size - The sidebar's size.
|
|
2000
|
+
* @returns The side of the square.
|
|
2001
|
+
*/
|
|
2002
|
+
function squared(size) {
|
|
2003
|
+
return `max({sizes.6}, ${dense(`{sizes.tag.${below(size)}}`)})`;
|
|
2004
|
+
}
|
|
2005
|
+
/**
|
|
2006
|
+
* Returns the size styles of a band: the content's padding, the gap between its children, the rows'
|
|
2007
|
+
* inset on its lines of words and the text style of the rows' words one size up.
|
|
2008
|
+
*
|
|
2009
|
+
* @param size - The sidebar's size.
|
|
2010
|
+
* @returns The styles of the header or the footer at that size.
|
|
2011
|
+
*/
|
|
2012
|
+
function banded(size) {
|
|
2013
|
+
return {
|
|
2014
|
+
textStyle: `label.${below(size)}`,
|
|
2015
|
+
gap: dense(`{spacing.gap.${below(size)}}`),
|
|
2016
|
+
[LINES]: { marginInline: marked(size) },
|
|
2017
|
+
padding: dense(`{spacing.gap.${size}}`)
|
|
2018
|
+
};
|
|
2019
|
+
}
|
|
2020
|
+
/**
|
|
2021
|
+
* Styles a plain sidebar at the middle size.
|
|
2022
|
+
*
|
|
2023
|
+
* @remarks
|
|
2024
|
+
* `staticCss` lists every size and every look. A sidebar in a panel over the page renders at `lg`
|
|
2025
|
+
* whatever its `size`, a product's frame states the size and the look as data, and the compiler
|
|
2026
|
+
* emits a variant only where a source states it as a literal.
|
|
2027
|
+
*/
|
|
2028
|
+
const recipe$2 = defineSlotRecipe({
|
|
2029
|
+
base: {
|
|
2030
|
+
content: {
|
|
2031
|
+
display: "flex",
|
|
2032
|
+
flexDirection: "column"
|
|
2033
|
+
},
|
|
2034
|
+
empty: {
|
|
2035
|
+
color: "fg.muted",
|
|
2036
|
+
[ICONIC]: { display: "none" },
|
|
2037
|
+
textAlign: "center"
|
|
2038
|
+
},
|
|
2039
|
+
footer: BANDED,
|
|
2040
|
+
header: BANDED,
|
|
2041
|
+
nav: {
|
|
2042
|
+
alignItems: "center",
|
|
2043
|
+
display: "grid",
|
|
2044
|
+
gridTemplateColumns: "minmax(0, 1fr) auto",
|
|
2045
|
+
minInlineSize: "0",
|
|
2046
|
+
"&[data-unmatched]:not(:has(.sidebar__empty))": { display: "none" },
|
|
2047
|
+
"& > *": { gridColumn: "1 / -1" }
|
|
2048
|
+
},
|
|
2049
|
+
navAction: {
|
|
2050
|
+
...interactive(),
|
|
2051
|
+
_active: { background: "colorPalette.emphasized" },
|
|
2052
|
+
_hover: {
|
|
2053
|
+
background: "colorPalette.muted",
|
|
2054
|
+
color: "fg"
|
|
2055
|
+
},
|
|
2056
|
+
alignItems: "center",
|
|
2057
|
+
appearance: "none",
|
|
2058
|
+
background: "transparent",
|
|
2059
|
+
borderRadius: "l1",
|
|
2060
|
+
color: "fg.muted",
|
|
2061
|
+
display: "flex",
|
|
2062
|
+
flexShrink: "0",
|
|
2063
|
+
gridColumn: "2 / 3",
|
|
2064
|
+
[ICONIC]: { display: "none" },
|
|
2065
|
+
justifyContent: "center",
|
|
2066
|
+
padding: "0"
|
|
2067
|
+
},
|
|
2068
|
+
navHeading: {
|
|
2069
|
+
...truncate(),
|
|
2070
|
+
alignItems: "center",
|
|
2071
|
+
color: "fg.subtle",
|
|
2072
|
+
display: "flex",
|
|
2073
|
+
fontWeight: "medium",
|
|
2074
|
+
gridColumn: "1 / 2",
|
|
2075
|
+
[ICONIC]: { srOnly: true },
|
|
2076
|
+
textTransform: "uppercase"
|
|
2077
|
+
},
|
|
2078
|
+
navLabel: {
|
|
2079
|
+
...truncate(),
|
|
2080
|
+
textStyle: "label.xs",
|
|
2081
|
+
alignItems: "center",
|
|
2082
|
+
color: "fg.subtle",
|
|
2083
|
+
display: "flex",
|
|
2084
|
+
fontWeight: "medium",
|
|
2085
|
+
gridColumn: "1 / 2",
|
|
2086
|
+
[ICONIC]: { srOnly: true },
|
|
2087
|
+
textTransform: "uppercase"
|
|
2088
|
+
},
|
|
2089
|
+
root: {
|
|
2090
|
+
blockSize: "100%",
|
|
2091
|
+
display: "flex",
|
|
2092
|
+
flexDirection: "column",
|
|
2093
|
+
minBlockSize: "0",
|
|
2094
|
+
minInlineSize: "0"
|
|
2095
|
+
},
|
|
2096
|
+
scroller: {
|
|
2097
|
+
flex: "1",
|
|
2098
|
+
minBlockSize: "0"
|
|
2099
|
+
},
|
|
2100
|
+
search: {
|
|
2101
|
+
[ICONIC]: {
|
|
2102
|
+
display: "flex",
|
|
2103
|
+
justifyContent: "center"
|
|
2104
|
+
},
|
|
2105
|
+
inlineSize: "full",
|
|
2106
|
+
minInlineSize: "0"
|
|
2107
|
+
},
|
|
2108
|
+
separator: divider("horizontal")
|
|
2109
|
+
},
|
|
2110
|
+
className: "sidebar",
|
|
2111
|
+
defaultVariants: {
|
|
2112
|
+
size: "md",
|
|
2113
|
+
variant: "plain"
|
|
2114
|
+
},
|
|
2115
|
+
jsx: [/^Sidebar(\.\w+)?$/u],
|
|
2116
|
+
slots: [
|
|
2117
|
+
"root",
|
|
2118
|
+
"header",
|
|
2119
|
+
"scroller",
|
|
2120
|
+
"content",
|
|
2121
|
+
"footer",
|
|
2122
|
+
"nav",
|
|
2123
|
+
"navLabel",
|
|
2124
|
+
"navHeading",
|
|
2125
|
+
"navAction",
|
|
2126
|
+
"search",
|
|
2127
|
+
"empty",
|
|
2128
|
+
"separator"
|
|
2129
|
+
],
|
|
2130
|
+
staticCss: [{
|
|
2131
|
+
size: [
|
|
2132
|
+
"sm",
|
|
2133
|
+
"md",
|
|
2134
|
+
"lg"
|
|
2135
|
+
],
|
|
2136
|
+
variant: [
|
|
2137
|
+
"outline",
|
|
2138
|
+
"plain",
|
|
2139
|
+
"subtle",
|
|
2140
|
+
"surface"
|
|
2141
|
+
]
|
|
2142
|
+
}],
|
|
2143
|
+
variants: {
|
|
2144
|
+
size: onSlots({
|
|
2145
|
+
/**
|
|
2146
|
+
* The blocks are three to four times as far apart as the rows inside them, so each block
|
|
2147
|
+
* reads as its own group. A separator between two blocks sets no margin of its own, so the
|
|
2148
|
+
* gap on either side of it is the block gap.
|
|
2149
|
+
*/
|
|
2150
|
+
content: sizeVariants((size) => ({
|
|
2151
|
+
"& > .sidebar__separator": { marginBlock: "0" },
|
|
2152
|
+
gap: dense(`{spacing.gap.${BLOCKS[size]}}`),
|
|
2153
|
+
padding: dense(`{spacing.gap.${size}}`)
|
|
2154
|
+
}), [
|
|
2155
|
+
"sm",
|
|
2156
|
+
"md",
|
|
2157
|
+
"lg"
|
|
2158
|
+
]),
|
|
2159
|
+
empty: sizeVariants((size) => ({
|
|
2160
|
+
textStyle: `body.${size}`,
|
|
2161
|
+
padding: dense(`{spacing.inset.${size}}`)
|
|
2162
|
+
}), [
|
|
2163
|
+
"sm",
|
|
2164
|
+
"md",
|
|
2165
|
+
"lg"
|
|
2166
|
+
]),
|
|
2167
|
+
footer: sizeVariants(banded, [
|
|
2168
|
+
"sm",
|
|
2169
|
+
"md",
|
|
2170
|
+
"lg"
|
|
2171
|
+
]),
|
|
2172
|
+
header: sizeVariants(banded, [
|
|
2173
|
+
"sm",
|
|
2174
|
+
"md",
|
|
2175
|
+
"lg"
|
|
2176
|
+
]),
|
|
2177
|
+
/**
|
|
2178
|
+
* The label and the first row are as far apart as two rows.
|
|
2179
|
+
*/
|
|
2180
|
+
nav: sizeVariants((size) => ({ gap: dense(`{spacing.gap.${below(below(size))}}`) }), [
|
|
2181
|
+
"sm",
|
|
2182
|
+
"md",
|
|
2183
|
+
"lg"
|
|
2184
|
+
]),
|
|
2185
|
+
/**
|
|
2186
|
+
* The block's control is a square in the rows' end column, with the rows' end inset after
|
|
2187
|
+
* it and an icon the size of a row's leading icon.
|
|
2188
|
+
*/
|
|
2189
|
+
navAction: sizeVariants((size) => ({
|
|
2190
|
+
"& > svg": { boxSize: dense(`{sizes.icon.${below(size)}}`) },
|
|
2191
|
+
blockSize: squared(size),
|
|
2192
|
+
marginInlineEnd: marked(size),
|
|
2193
|
+
minInlineSize: squared(size)
|
|
2194
|
+
}), [
|
|
2195
|
+
"sm",
|
|
2196
|
+
"md",
|
|
2197
|
+
"lg"
|
|
2198
|
+
]),
|
|
2199
|
+
navHeading: sizeVariants((size) => ({
|
|
2200
|
+
textStyle: "label.xs",
|
|
2201
|
+
marginBlockEnd: `calc(${dense(`{spacing.gap.${size}}`)} * -0.5)`,
|
|
2202
|
+
paddingBlock: dense("{spacing.gap.xs}"),
|
|
2203
|
+
paddingInline: marked(size)
|
|
2204
|
+
}), [
|
|
2205
|
+
"sm",
|
|
2206
|
+
"md",
|
|
2207
|
+
"lg"
|
|
2208
|
+
]),
|
|
2209
|
+
/**
|
|
2210
|
+
* The label is as tall as a row of the navigation list, so it reads as the list's first row.
|
|
2211
|
+
*/
|
|
2212
|
+
navLabel: sizeVariants((size) => ({
|
|
2213
|
+
blockSize: `max({sizes.6}, ${dense(`{sizes.${ROWS[size]}}`)})`,
|
|
2214
|
+
paddingInline: marked(size)
|
|
2215
|
+
}), [
|
|
2216
|
+
"sm",
|
|
2217
|
+
"md",
|
|
2218
|
+
"lg"
|
|
2219
|
+
]),
|
|
2220
|
+
/**
|
|
2221
|
+
* The search has zero inline padding, so the field is as wide as the rows under it.
|
|
2222
|
+
*/
|
|
2223
|
+
search: sizeVariants(() => ({ paddingInline: "0" }), [
|
|
2224
|
+
"sm",
|
|
2225
|
+
"md",
|
|
2226
|
+
"lg"
|
|
2227
|
+
]),
|
|
2228
|
+
separator: sizeVariants((size) => ({ marginBlock: dense(`{spacing.gap.${size}}`) }), [
|
|
2229
|
+
"sm",
|
|
2230
|
+
"md",
|
|
2231
|
+
"lg"
|
|
2232
|
+
])
|
|
2233
|
+
}),
|
|
2234
|
+
/**
|
|
2235
|
+
* The ground and the edge of the sidebar.
|
|
2236
|
+
*
|
|
2237
|
+
* @remarks
|
|
2238
|
+
* `subtle` is a muted ground without an edge, for a sidebar inside an app shell panel. The
|
|
2239
|
+
* shell renders the hairline between the panel and the page.
|
|
2240
|
+
*/
|
|
2241
|
+
variant: onSlot("root", {
|
|
2242
|
+
subtle: { background: "bg.subtle" },
|
|
2243
|
+
surface: {
|
|
2244
|
+
...surface(),
|
|
2245
|
+
borderRadius: "0"
|
|
2246
|
+
},
|
|
2247
|
+
outline: {
|
|
2248
|
+
borderColor: "border",
|
|
2249
|
+
borderInlineEndWidth: "hairline"
|
|
2250
|
+
},
|
|
2251
|
+
plain: { background: "transparent" }
|
|
2252
|
+
})
|
|
2253
|
+
}
|
|
2254
|
+
});
|
|
2255
|
+
//#endregion
|
|
2256
|
+
//#region src/splitter/recipe.ts
|
|
2257
|
+
/**
|
|
2258
|
+
* Recipe for a splitter: panels in a row or a column, with a resize trigger between two panels.
|
|
2259
|
+
*
|
|
2260
|
+
* @remarks
|
|
2261
|
+
* The root is a flex row or column that fills its container: it grows in a flex container and is
|
|
2262
|
+
* as tall and as wide as any other container. The machine sizes each panel inline, and a panel is
|
|
2263
|
+
* a column its content can shrink in, so a scroll area inside it scrolls. The trigger is a strip
|
|
2264
|
+
* `sizes.2` across, pulled over both panels by half its width, so the panels share the root as if
|
|
2265
|
+
* it took no room. It stacks at `docked`, above a scroll area or other positioned content in the
|
|
2266
|
+
* panel after it, which would take the pointer off half the strip. Under a coarse pointer a
|
|
2267
|
+
* transparent pseudo-element widens the strip to `sizes.6`, the WCAG 2.5.8 target. The separator
|
|
2268
|
+
* is a hairline along the strip's middle, and the indicator a pill `sizes.6` long on its centre.
|
|
2269
|
+
* The line and the pill take the palette's solid while the pointer is over the strip, while it
|
|
2270
|
+
* has focus and while it drags. The focus ring is on the pill, because the strip's own box is
|
|
2271
|
+
* narrower than a ring, and on the strip of a trigger rendered without the pill. A disabled
|
|
2272
|
+
* trigger keeps the line and hides the pill. Under forced colours the line is `CanvasText`, an
|
|
2273
|
+
* active pill `Highlight` and the pill's ring `CanvasText`.
|
|
2274
|
+
*/
|
|
2275
|
+
/**
|
|
2276
|
+
* Class name of the recipe, which a selector across its parts writes.
|
|
2277
|
+
*/
|
|
2278
|
+
const CLASS$1 = "splitter";
|
|
2279
|
+
/**
|
|
2280
|
+
* Width of the trigger's strip across its line.
|
|
2281
|
+
*/
|
|
2282
|
+
const STRIP = "{sizes.2}";
|
|
2283
|
+
/**
|
|
2284
|
+
* Width of the strip under a coarse pointer, the WCAG 2.5.8 target.
|
|
2285
|
+
*/
|
|
2286
|
+
const TOUCH = "{sizes.6}";
|
|
2287
|
+
/**
|
|
2288
|
+
* Length of the indicator's pill along the line.
|
|
2289
|
+
*/
|
|
2290
|
+
const PILL = "{sizes.6}";
|
|
2291
|
+
/**
|
|
2292
|
+
* Selects a part of a splitter whose panels sit in a row, where a trigger's line is vertical.
|
|
2293
|
+
*/
|
|
2294
|
+
const ACROSS = "&[data-orientation=horizontal]";
|
|
2295
|
+
/**
|
|
2296
|
+
* Selects a part of a splitter whose panels sit in a column, where a trigger's line is horizontal.
|
|
2297
|
+
*/
|
|
2298
|
+
const DOWN = "&[data-orientation=vertical]";
|
|
2299
|
+
/**
|
|
2300
|
+
* Selects a line or a pill while its trigger is under the pointer, has focus or drags.
|
|
2301
|
+
*
|
|
2302
|
+
* @remarks
|
|
2303
|
+
* `data-focus-visible` is the attribute a still scene stages focus with.
|
|
2304
|
+
*/
|
|
2305
|
+
const ACTIVE = `.${CLASS$1}__resizeTrigger:is(:hover, [data-focus], [data-focus-visible], [data-dragging]):not([data-disabled]) > &`;
|
|
2306
|
+
/**
|
|
2307
|
+
* Selects the pill while its trigger has keyboard focus.
|
|
2308
|
+
*/
|
|
2309
|
+
const RINGED = `.${CLASS$1}__resizeTrigger:is(:focus-visible, [data-focus-visible]) > &`;
|
|
2310
|
+
/**
|
|
2311
|
+
* Selects a trigger that renders the pill, which then takes the trigger's focus ring.
|
|
2312
|
+
*/
|
|
2313
|
+
const PILLED = `&:has(> .${CLASS$1}__resizeTriggerIndicator)`;
|
|
2314
|
+
/**
|
|
2315
|
+
* Defines the splitter recipe, in the primary palette by default.
|
|
2316
|
+
*/
|
|
2317
|
+
const recipe$1 = defineSlotRecipe({
|
|
2318
|
+
base: {
|
|
2319
|
+
panel: {
|
|
2320
|
+
display: "flex",
|
|
2321
|
+
flexDirection: "column",
|
|
2322
|
+
minBlockSize: "0",
|
|
2323
|
+
minInlineSize: "0"
|
|
2324
|
+
},
|
|
2325
|
+
resizeTrigger: {
|
|
2326
|
+
focusVisibleRing: "outside",
|
|
2327
|
+
_touch: {
|
|
2328
|
+
_before: {
|
|
2329
|
+
content: "\"\"",
|
|
2330
|
+
position: "absolute"
|
|
2331
|
+
},
|
|
2332
|
+
[ACROSS]: { _before: {
|
|
2333
|
+
insetBlock: "0",
|
|
2334
|
+
insetInline: `calc((${TOUCH} - ${STRIP}) * -0.5)`
|
|
2335
|
+
} },
|
|
2336
|
+
[DOWN]: { _before: {
|
|
2337
|
+
insetBlock: `calc((${TOUCH} - ${STRIP}) * -0.5)`,
|
|
2338
|
+
insetInline: "0"
|
|
2339
|
+
} }
|
|
2340
|
+
},
|
|
2341
|
+
[ACROSS]: {
|
|
2342
|
+
inlineSize: STRIP,
|
|
2343
|
+
marginInline: `calc(${STRIP} * -0.5)`
|
|
2344
|
+
},
|
|
2345
|
+
alignItems: "center",
|
|
2346
|
+
display: "grid",
|
|
2347
|
+
[DOWN]: {
|
|
2348
|
+
blockSize: STRIP,
|
|
2349
|
+
marginBlock: `calc(${STRIP} * -0.5)`
|
|
2350
|
+
},
|
|
2351
|
+
focusRingColor: "colorPalette.focusRing",
|
|
2352
|
+
justifyItems: "center",
|
|
2353
|
+
[PILLED]: { _focusVisible: { outlineStyle: "none" } },
|
|
2354
|
+
position: "relative",
|
|
2355
|
+
zIndex: "docked"
|
|
2356
|
+
},
|
|
2357
|
+
resizeTriggerIndicator: {
|
|
2358
|
+
_highContrast: {
|
|
2359
|
+
background: "Canvas",
|
|
2360
|
+
borderColor: "CanvasText",
|
|
2361
|
+
forcedColorAdjust: "none"
|
|
2362
|
+
},
|
|
2363
|
+
"&[data-disabled]": { visibility: "hidden" },
|
|
2364
|
+
[ACROSS]: {
|
|
2365
|
+
blockSize: PILL,
|
|
2366
|
+
inlineSize: STRIP
|
|
2367
|
+
},
|
|
2368
|
+
[ACTIVE]: {
|
|
2369
|
+
_highContrast: {
|
|
2370
|
+
background: "Highlight",
|
|
2371
|
+
borderColor: "Highlight"
|
|
2372
|
+
},
|
|
2373
|
+
background: "colorPalette.solid",
|
|
2374
|
+
borderColor: "colorPalette.solid"
|
|
2375
|
+
},
|
|
2376
|
+
background: "bg.panel",
|
|
2377
|
+
borderColor: "border.emphasized",
|
|
2378
|
+
borderRadius: "full",
|
|
2379
|
+
borderStyle: "solid",
|
|
2380
|
+
borderWidth: "hairline",
|
|
2381
|
+
boxShadow: "xs",
|
|
2382
|
+
[DOWN]: {
|
|
2383
|
+
blockSize: STRIP,
|
|
2384
|
+
inlineSize: PILL
|
|
2385
|
+
},
|
|
2386
|
+
gridArea: "1 / 1 / 2 / 2",
|
|
2387
|
+
position: "relative",
|
|
2388
|
+
[RINGED]: {
|
|
2389
|
+
_highContrast: { outlineColor: "CanvasText" },
|
|
2390
|
+
outlineColor: "colorPalette.focusRing",
|
|
2391
|
+
outlineOffset: "{spacing.ring}",
|
|
2392
|
+
outlineStyle: "solid",
|
|
2393
|
+
outlineWidth: "ring"
|
|
2394
|
+
},
|
|
2395
|
+
transitionDuration: "fast",
|
|
2396
|
+
transitionProperty: "common"
|
|
2397
|
+
},
|
|
2398
|
+
resizeTriggerSeparator: {
|
|
2399
|
+
_highContrast: {
|
|
2400
|
+
background: "CanvasText",
|
|
2401
|
+
forcedColorAdjust: "none"
|
|
2402
|
+
},
|
|
2403
|
+
[ACROSS]: {
|
|
2404
|
+
blockSize: "full",
|
|
2405
|
+
inlineSize: "{borderWidths.hairline}"
|
|
2406
|
+
},
|
|
2407
|
+
[ACTIVE]: {
|
|
2408
|
+
_highContrast: { background: "Highlight" },
|
|
2409
|
+
background: "colorPalette.solid"
|
|
2410
|
+
},
|
|
2411
|
+
background: "border",
|
|
2412
|
+
[DOWN]: {
|
|
2413
|
+
blockSize: "{borderWidths.hairline}",
|
|
2414
|
+
inlineSize: "full"
|
|
2415
|
+
},
|
|
2416
|
+
gridArea: "1 / 1 / 2 / 2",
|
|
2417
|
+
transitionDuration: "fast",
|
|
2418
|
+
transitionProperty: "common"
|
|
2419
|
+
},
|
|
2420
|
+
root: {
|
|
2421
|
+
[ACROSS]: { flexDirection: "row" },
|
|
2422
|
+
blockSize: "full",
|
|
2423
|
+
colorPalette: "primary",
|
|
2424
|
+
display: "flex",
|
|
2425
|
+
[DOWN]: { flexDirection: "column" },
|
|
2426
|
+
flex: "1",
|
|
2427
|
+
inlineSize: "full",
|
|
2428
|
+
minBlockSize: "0",
|
|
2429
|
+
minInlineSize: "0",
|
|
2430
|
+
overflow: "hidden"
|
|
2431
|
+
}
|
|
2432
|
+
},
|
|
2433
|
+
className: CLASS$1,
|
|
2434
|
+
jsx: [/^Splitter(\.\w+)?$/u],
|
|
2435
|
+
slots: [
|
|
2436
|
+
"root",
|
|
2437
|
+
"panel",
|
|
2438
|
+
"resizeTrigger",
|
|
2439
|
+
"resizeTriggerSeparator",
|
|
2440
|
+
"resizeTriggerIndicator"
|
|
2441
|
+
],
|
|
2442
|
+
variants: {
|
|
2443
|
+
/**
|
|
2444
|
+
* Semantic palette of the line and the pill while the pointer is over a trigger, while it has
|
|
2445
|
+
* focus and while it drags, and of the focus ring. Without a value the root reads `primary`.
|
|
2446
|
+
*/
|
|
2447
|
+
palette: onSlot("root", paletteVariants()) }
|
|
2448
|
+
});
|
|
2449
|
+
//#endregion
|
|
2450
|
+
//#region src/toolbar/recipe.ts
|
|
2451
|
+
/**
|
|
2452
|
+
* Recipe for a toolbar: a row of controls with a start, a centre and an end band, and a search.
|
|
2453
|
+
*
|
|
2454
|
+
* @remarks
|
|
2455
|
+
* A band left out takes no room. The centre takes the room the other bands leave and truncates a
|
|
2456
|
+
* long title, because a toolbar on two lines moves everything under it. The root sets the gap as
|
|
2457
|
+
* `--toolbar-gap`, which every band reads: the gap token of the toolbar's size, 8px at `md`. The
|
|
2458
|
+
* layout package's `Group` joins controls, and the separator is the layout divider stretched to
|
|
2459
|
+
* the row's height. The search is 15rem wide and does not grow, because the centre takes the free
|
|
2460
|
+
* room. As a child of the row it shrinks down to zero before a band's controls do. A field's
|
|
2461
|
+
* percentage width gives a band no content width, so the search states an inline size. An opened
|
|
2462
|
+
* search covers the whole row, the padding and the edge of an outline or surface row included,
|
|
2463
|
+
* and the root hides its other children while it is open, so the row needs no fill of its own on
|
|
2464
|
+
* any surface. The title is semibold and leaves twice the gap after it, so the control after it
|
|
2465
|
+
* reads as the first of the row's controls and not as part of the title. A narrow row folds
|
|
2466
|
+
* those controls, and its title leaves the row's gap alone. The recipe has no
|
|
2467
|
+
* `palette` axis, because the controls in the row set their own palettes, and no `effect` axis,
|
|
2468
|
+
* because the row is not a control.
|
|
2469
|
+
*/
|
|
2470
|
+
/**
|
|
2471
|
+
* Custom property the root sets to the gap between controls, which every band reads.
|
|
2472
|
+
*/
|
|
2473
|
+
const GAP = "--toolbar-gap";
|
|
2474
|
+
/**
|
|
2475
|
+
* Custom property the root sets to its own padding in the outline and surface looks.
|
|
2476
|
+
*/
|
|
2477
|
+
const INSET = "--toolbar-inset";
|
|
2478
|
+
/**
|
|
2479
|
+
* Custom property the outline and surface looks set to the root's edge width, which an opened
|
|
2480
|
+
* search covers.
|
|
2481
|
+
*/
|
|
2482
|
+
const EDGE = "--toolbar-edge";
|
|
2483
|
+
/**
|
|
2484
|
+
* Styles every band: a row of controls, centred on the cross axis.
|
|
2485
|
+
*/
|
|
2486
|
+
const BAND = {
|
|
2487
|
+
alignItems: "center",
|
|
2488
|
+
display: "flex",
|
|
2489
|
+
gap: `var(${GAP})`,
|
|
2490
|
+
minInlineSize: "0"
|
|
2491
|
+
};
|
|
2492
|
+
/**
|
|
2493
|
+
* Class name of the recipe, which a selector across parts reads.
|
|
2494
|
+
*/
|
|
2495
|
+
const CLASS = "toolbar";
|
|
2496
|
+
/**
|
|
2497
|
+
* Selects a root while its search is open.
|
|
2498
|
+
*/
|
|
2499
|
+
const SEARCHING = `&:has(.${CLASS}__search[data-opened])`;
|
|
2500
|
+
/**
|
|
2501
|
+
* Defines the toolbar recipe: a plain row at size `md` with `l2` corners by default.
|
|
2502
|
+
*/
|
|
2503
|
+
const recipe = defineSlotRecipe({
|
|
2504
|
+
base: {
|
|
2505
|
+
action: {
|
|
2506
|
+
...FOLDING,
|
|
2507
|
+
flexShrink: "0"
|
|
2508
|
+
},
|
|
2509
|
+
center: {
|
|
2510
|
+
...BAND,
|
|
2511
|
+
...truncate(),
|
|
2512
|
+
"& > *": {
|
|
2513
|
+
minInlineSize: "0",
|
|
2514
|
+
overflow: "hidden",
|
|
2515
|
+
textOverflow: "ellipsis"
|
|
2516
|
+
},
|
|
2517
|
+
flex: "1",
|
|
2518
|
+
justifyContent: "center"
|
|
2519
|
+
},
|
|
2520
|
+
end: {
|
|
2521
|
+
...BAND,
|
|
2522
|
+
flexShrink: "0",
|
|
2523
|
+
marginInlineStart: "auto"
|
|
2524
|
+
},
|
|
2525
|
+
group: { flexShrink: "0" },
|
|
2526
|
+
root: {
|
|
2527
|
+
...BAND,
|
|
2528
|
+
inlineSize: "100%",
|
|
2529
|
+
position: "relative",
|
|
2530
|
+
[SEARCHING]: { [`& > :not(.${CLASS}__search)`]: { visibility: "hidden" } }
|
|
2531
|
+
},
|
|
2532
|
+
search: {
|
|
2533
|
+
"&[data-opened]": {
|
|
2534
|
+
"& > :first-child": { flex: "1" },
|
|
2535
|
+
alignItems: "stretch",
|
|
2536
|
+
display: "flex",
|
|
2537
|
+
inlineSize: "auto",
|
|
2538
|
+
inset: `calc(var(${EDGE}, 0px) * -1)`,
|
|
2539
|
+
position: "absolute",
|
|
2540
|
+
zIndex: "1"
|
|
2541
|
+
},
|
|
2542
|
+
"& > *": { minInlineSize: "0" },
|
|
2543
|
+
flex: "0 1 auto",
|
|
2544
|
+
inlineSize: "60",
|
|
2545
|
+
minInlineSize: "0"
|
|
2546
|
+
},
|
|
2547
|
+
separator: {
|
|
2548
|
+
alignSelf: "stretch",
|
|
2549
|
+
blockSize: "auto"
|
|
2550
|
+
},
|
|
2551
|
+
start: {
|
|
2552
|
+
...BAND,
|
|
2553
|
+
flexShrink: "0"
|
|
2554
|
+
},
|
|
2555
|
+
title: {
|
|
2556
|
+
...BAND,
|
|
2557
|
+
[`&[${NARROW$1}]`]: { marginInlineEnd: "0" },
|
|
2558
|
+
flexShrink: "0",
|
|
2559
|
+
fontWeight: "semibold",
|
|
2560
|
+
marginInlineEnd: `calc(var(${GAP}) * 2)`
|
|
2561
|
+
}
|
|
2562
|
+
},
|
|
2563
|
+
className: CLASS,
|
|
2564
|
+
defaultVariants: {
|
|
2565
|
+
radius: "l2",
|
|
2566
|
+
size: "md",
|
|
2567
|
+
variant: "plain"
|
|
2568
|
+
},
|
|
2569
|
+
jsx: [/^Toolbar(\.\w+)?$/u],
|
|
2570
|
+
slots: [
|
|
2571
|
+
"root",
|
|
2572
|
+
"start",
|
|
2573
|
+
"center",
|
|
2574
|
+
"end",
|
|
2575
|
+
"action",
|
|
2576
|
+
"group",
|
|
2577
|
+
"separator",
|
|
2578
|
+
"search",
|
|
2579
|
+
"title"
|
|
2580
|
+
],
|
|
2581
|
+
variants: {
|
|
2582
|
+
/**
|
|
2583
|
+
* Corner radius of an outlined or surface row.
|
|
2584
|
+
*/
|
|
2585
|
+
radius: onSlot("root", cornerVariants([
|
|
2586
|
+
"l1",
|
|
2587
|
+
"l2",
|
|
2588
|
+
"l3"
|
|
2589
|
+
])),
|
|
2590
|
+
/**
|
|
2591
|
+
* Size of the gap and of the separator's block margin.
|
|
2592
|
+
*/
|
|
2593
|
+
size: onSlots({
|
|
2594
|
+
root: sizeVariants((size) => ({ [GAP]: `{spacing.gap.${size}}` })),
|
|
2595
|
+
separator: sizeVariants((size) => ({ marginBlock: dense(`{spacing.gap.${size}}`) }))
|
|
2596
|
+
}),
|
|
2597
|
+
/**
|
|
2598
|
+
* Look of the row.
|
|
2599
|
+
*
|
|
2600
|
+
* @remarks
|
|
2601
|
+
* `outline` and `surface` pad the row by its gap, so the controls start inside the row's
|
|
2602
|
+
* edge. `plain` has no edge and no padding.
|
|
2603
|
+
*/
|
|
2604
|
+
variant: {
|
|
2605
|
+
surface: { root: {
|
|
2606
|
+
...surface(),
|
|
2607
|
+
[EDGE]: "{borderWidths.hairline}",
|
|
2608
|
+
[INSET]: `var(${GAP})`,
|
|
2609
|
+
padding: `var(${INSET})`
|
|
2610
|
+
} },
|
|
2611
|
+
outline: { root: {
|
|
2612
|
+
borderColor: "border",
|
|
2613
|
+
borderWidth: "hairline",
|
|
2614
|
+
[EDGE]: "{borderWidths.hairline}",
|
|
2615
|
+
[INSET]: `var(${GAP})`,
|
|
2616
|
+
padding: `var(${INSET})`
|
|
2617
|
+
} },
|
|
2618
|
+
plain: { root: { background: "transparent" } }
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
});
|
|
2622
|
+
//#endregion
|
|
2623
|
+
export { lengthOf as C, keyedWidth as S, STICKY_OFFSET$1 as _, SCROLLPORT as a, clamped as b, STICKY_TOP as c, recipe$6 as d, PANEL_RAIL as f, SETTLED as g, PANE_TOP as h, recipe$3 as i, recipe$4 as l, PANEL_TOP as m, recipe$1 as n, SIDEBAR_SIZE as o, PANEL_SIZE as p, recipe$2 as r, STICKY_OFFSET as s, recipe as t, recipe$5 as u, WINDOW_HEIGHT as v, recipe$7 as w, growthOf as x, RESIZING as y };
|