@stealthscale/component-media 0.1.0
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 +487 -0
- package/dist/cyclonedx/bom.json +1 -0
- package/dist/index.d.ts +700 -0
- package/dist/index.js +1890 -0
- package/dist/recipe-m4bX2c06.js +749 -0
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/theme.d.ts +11 -0
- package/dist/theme.js +28 -0
- package/package.json +87 -0
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
import { PALETTES, cornerVariants, defineRecipe, defineSlotRecipe, dense, flatVariants, onSlot, onSlots, paletteVariants, ratioVariants, sizeVariants } from "@stealthscale/theme/authoring";
|
|
2
|
+
//#region src/audio/recipe.ts
|
|
3
|
+
/**
|
|
4
|
+
* Styles an `audio` element as a block as wide as its container.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* The browser draws the controls, so the recipe sets the box and the focus ring alone and offers
|
|
8
|
+
* no axis. A radius on the element clips Chromium's control panel, and the panel's colours follow
|
|
9
|
+
* the page's `color-scheme`.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Styles an audio element that fills the inline size of its container.
|
|
13
|
+
*/
|
|
14
|
+
const recipe$6 = defineRecipe({
|
|
15
|
+
base: {
|
|
16
|
+
display: "block",
|
|
17
|
+
focusRingColor: "colorPalette.focusRing",
|
|
18
|
+
focusVisibleRing: "outside",
|
|
19
|
+
inlineSize: "full",
|
|
20
|
+
maxInlineSize: "full"
|
|
21
|
+
},
|
|
22
|
+
className: "audio",
|
|
23
|
+
jsx: [/^Audio$/u]
|
|
24
|
+
});
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/avatar/recipe.ts
|
|
27
|
+
/**
|
|
28
|
+
* Declares the avatar's slot recipe: a box with a person's picture, or with their initials while
|
|
29
|
+
* the picture is missing, loading or broken, and a group that overlaps several boxes.
|
|
30
|
+
*
|
|
31
|
+
* @remarks
|
|
32
|
+
* The box is a square on the control scale, so an avatar is as tall as a button of its size: 32,
|
|
33
|
+
* 36, 40, 44, 48 and 56px from `xs` to `2xl`. `2xs` is the middle tag height, 24px, for a byline
|
|
34
|
+
* or a list row. The root sets its side as `--avatar-size`, and a group overlaps each avatar with
|
|
35
|
+
* the one before it by a quarter of that side. The image covers the box and takes the root's
|
|
36
|
+
* corners, and the root clips nothing, so a badge can overhang its edge. The fallback centres the
|
|
37
|
+
* initials in the label style of the avatar's size. The looks are the flat layer styles, because
|
|
38
|
+
* an avatar is read and not pressed, and the look and the palette color the fallback alone. A
|
|
39
|
+
* group rings each avatar in `bg.panel` with an outline, which is drawn outside the box and
|
|
40
|
+
* leaves the fill's curved edge alone. Under forced colors the box keeps a hairline `CanvasText`
|
|
41
|
+
* outline, because a look without an edge loses its shape there.
|
|
42
|
+
*/
|
|
43
|
+
/**
|
|
44
|
+
* Custom property the root sets to its side, which a group and a badge read.
|
|
45
|
+
*/
|
|
46
|
+
const SIZE = "--avatar-size";
|
|
47
|
+
/**
|
|
48
|
+
* Sizes the avatar offers on the control scale.
|
|
49
|
+
*/
|
|
50
|
+
const SIZES = [
|
|
51
|
+
"xs",
|
|
52
|
+
"sm",
|
|
53
|
+
"md",
|
|
54
|
+
"lg",
|
|
55
|
+
"xl",
|
|
56
|
+
"2xl"
|
|
57
|
+
];
|
|
58
|
+
/**
|
|
59
|
+
* Styles an element the machine hides with the `hidden` attribute, which a `display` value in the
|
|
60
|
+
* recipe would override.
|
|
61
|
+
*/
|
|
62
|
+
const HIDDEN = { "&[hidden]": { display: "none" } };
|
|
63
|
+
/**
|
|
64
|
+
* Styles a subtle circle at the middle size in the neutral palette.
|
|
65
|
+
*/
|
|
66
|
+
const recipe$5 = defineSlotRecipe({
|
|
67
|
+
base: {
|
|
68
|
+
fallback: {
|
|
69
|
+
...HIDDEN,
|
|
70
|
+
"& > svg": {
|
|
71
|
+
blockSize: "50%",
|
|
72
|
+
inlineSize: "50%"
|
|
73
|
+
},
|
|
74
|
+
alignItems: "center",
|
|
75
|
+
blockSize: "full",
|
|
76
|
+
display: "flex",
|
|
77
|
+
inlineSize: "full",
|
|
78
|
+
justifyContent: "center",
|
|
79
|
+
lineHeight: "1",
|
|
80
|
+
textTransform: "uppercase"
|
|
81
|
+
},
|
|
82
|
+
group: {
|
|
83
|
+
"& > .avatar__root": {
|
|
84
|
+
outlineColor: "bg.panel",
|
|
85
|
+
outlineStyle: "solid",
|
|
86
|
+
outlineWidth: "indicator"
|
|
87
|
+
},
|
|
88
|
+
"& > .avatar__root + .avatar__root": { marginInlineStart: `calc(var(${SIZE}) / -4)` },
|
|
89
|
+
alignItems: "center",
|
|
90
|
+
display: "inline-flex",
|
|
91
|
+
flexShrink: "0",
|
|
92
|
+
verticalAlign: "middle"
|
|
93
|
+
},
|
|
94
|
+
image: {
|
|
95
|
+
...HIDDEN,
|
|
96
|
+
blockSize: "full",
|
|
97
|
+
borderRadius: "inherit",
|
|
98
|
+
display: "block",
|
|
99
|
+
inlineSize: "full",
|
|
100
|
+
objectFit: "cover"
|
|
101
|
+
},
|
|
102
|
+
root: {
|
|
103
|
+
_highContrast: {
|
|
104
|
+
outlineColor: "CanvasText",
|
|
105
|
+
outlineOffset: "calc({borderWidths.hairline} * -1)",
|
|
106
|
+
outlineStyle: "solid",
|
|
107
|
+
outlineWidth: "hairline"
|
|
108
|
+
},
|
|
109
|
+
alignItems: "center",
|
|
110
|
+
blockSize: `var(${SIZE})`,
|
|
111
|
+
colorPalette: "neutral",
|
|
112
|
+
display: "inline-flex",
|
|
113
|
+
flexShrink: "0",
|
|
114
|
+
fontWeight: "medium",
|
|
115
|
+
inlineSize: `var(${SIZE})`,
|
|
116
|
+
justifyContent: "center",
|
|
117
|
+
position: "relative",
|
|
118
|
+
userSelect: "none",
|
|
119
|
+
verticalAlign: "middle"
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
className: "avatar",
|
|
123
|
+
defaultVariants: {
|
|
124
|
+
shape: "circle",
|
|
125
|
+
size: "md",
|
|
126
|
+
variant: "subtle"
|
|
127
|
+
},
|
|
128
|
+
jsx: [/^Avatar\.\w+$/u],
|
|
129
|
+
slots: [
|
|
130
|
+
"root",
|
|
131
|
+
"image",
|
|
132
|
+
"fallback",
|
|
133
|
+
"group"
|
|
134
|
+
],
|
|
135
|
+
staticCss: [{ palette: [...PALETTES] }],
|
|
136
|
+
variants: {
|
|
137
|
+
/**
|
|
138
|
+
* The halo around the box, in the palette's solid at half opacity.
|
|
139
|
+
*
|
|
140
|
+
* @remarks
|
|
141
|
+
* `pulse` animates the halo and stops under reduced motion, for a person who is speaking.
|
|
142
|
+
*/
|
|
143
|
+
effect: onSlot("root", {
|
|
144
|
+
glow: { layerStyle: "glow.sm" },
|
|
145
|
+
pulse: {
|
|
146
|
+
animationStyle: "pulse-glow",
|
|
147
|
+
boxShadowColor: "colorPalette.solid/50"
|
|
148
|
+
}
|
|
149
|
+
}),
|
|
150
|
+
palette: onSlot("root", paletteVariants()),
|
|
151
|
+
/**
|
|
152
|
+
* The corners of the box: a circle for a person, and a rounded or square box for a company, a
|
|
153
|
+
* service or a bot.
|
|
154
|
+
*/
|
|
155
|
+
shape: onSlot("root", {
|
|
156
|
+
circle: { borderRadius: "full" },
|
|
157
|
+
rounded: { borderRadius: "l2" },
|
|
158
|
+
square: { borderRadius: "none" }
|
|
159
|
+
}),
|
|
160
|
+
/**
|
|
161
|
+
* The side of the box and the initials in the label style of that size.
|
|
162
|
+
*
|
|
163
|
+
* @remarks
|
|
164
|
+
* `2xs` is the middle tag height with the smallest text style, because the control scale and
|
|
165
|
+
* the label role start at `xs`.
|
|
166
|
+
*/
|
|
167
|
+
size: onSlots({
|
|
168
|
+
fallback: {
|
|
169
|
+
"2xs": { textStyle: "2xs" },
|
|
170
|
+
...sizeVariants((size) => ({ textStyle: `label.${size}` }), SIZES)
|
|
171
|
+
},
|
|
172
|
+
root: {
|
|
173
|
+
"2xs": { [SIZE]: dense("{sizes.tag.md}") },
|
|
174
|
+
...sizeVariants((size) => ({ [SIZE]: dense(`{sizes.control.${size}}`) }), SIZES)
|
|
175
|
+
}
|
|
176
|
+
}),
|
|
177
|
+
variant: onSlot("root", flatVariants([
|
|
178
|
+
"solid",
|
|
179
|
+
"subtle",
|
|
180
|
+
"surface",
|
|
181
|
+
"outline"
|
|
182
|
+
]))
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
//#endregion
|
|
186
|
+
//#region src/avatar/avatar-badge.recipe.ts
|
|
187
|
+
/**
|
|
188
|
+
* Declares the avatar badge's recipe: a mark pinned to one corner of an avatar, half over the
|
|
189
|
+
* picture, which is a status dot when empty and grows around a count, an emoji or an icon.
|
|
190
|
+
*
|
|
191
|
+
* @remarks
|
|
192
|
+
* The badge centres on the point where the avatar's diagonal crosses its rim, 14.64% of the side
|
|
193
|
+
* in from the corner, and a rounded or square avatar takes the same point. An empty badge is 28%
|
|
194
|
+
* of the avatar's side and never under 8px. A badge with content is 40% of the side and never
|
|
195
|
+
* under 16px, its text is 60% of its own height, and a longer count widens it into a pill. The
|
|
196
|
+
* ring is an outline in `bg.panel`, which cuts the badge away from the picture behind it. Under
|
|
197
|
+
* forced colors the badge keeps its palette, and its label states the same thing in words.
|
|
198
|
+
*/
|
|
199
|
+
/**
|
|
200
|
+
* Custom property the badge sets to its own side.
|
|
201
|
+
*/
|
|
202
|
+
const BADGE = "--avatar-badge-size";
|
|
203
|
+
/**
|
|
204
|
+
* Distance from the avatar's edges to the point where its diagonal crosses its rim.
|
|
205
|
+
*/
|
|
206
|
+
const RIM = `calc(var(${SIZE}) * 0.1464)`;
|
|
207
|
+
/**
|
|
208
|
+
* Styles a neutral dot in the solid look on the avatar's bottom end corner.
|
|
209
|
+
*/
|
|
210
|
+
const recipe$4 = defineRecipe({
|
|
211
|
+
base: {
|
|
212
|
+
_highContrast: { forcedColorAdjust: "none" },
|
|
213
|
+
"&:not(:empty)": {
|
|
214
|
+
[BADGE]: `max({sizes.4}, calc(var(${SIZE}) * 0.4))`,
|
|
215
|
+
fontSize: `calc(var(${BADGE}) * 0.6)`,
|
|
216
|
+
paddingInline: `calc(var(${BADGE}) * 0.2)`
|
|
217
|
+
},
|
|
218
|
+
"& > svg": {
|
|
219
|
+
blockSize: "75%",
|
|
220
|
+
inlineSize: "75%"
|
|
221
|
+
},
|
|
222
|
+
alignItems: "center",
|
|
223
|
+
[BADGE]: `max({sizes.2}, calc(var(${SIZE}) * 0.28))`,
|
|
224
|
+
blockSize: `var(${BADGE})`,
|
|
225
|
+
borderRadius: "full",
|
|
226
|
+
display: "inline-flex",
|
|
227
|
+
fontVariantNumeric: "tabular-nums",
|
|
228
|
+
fontWeight: "semibold",
|
|
229
|
+
justifyContent: "center",
|
|
230
|
+
lineHeight: "1",
|
|
231
|
+
minInlineSize: `var(${BADGE})`,
|
|
232
|
+
outlineColor: "bg.panel",
|
|
233
|
+
outlineStyle: "solid",
|
|
234
|
+
outlineWidth: "control",
|
|
235
|
+
position: "absolute",
|
|
236
|
+
whiteSpace: "nowrap"
|
|
237
|
+
},
|
|
238
|
+
className: "avatar-badge",
|
|
239
|
+
defaultVariants: {
|
|
240
|
+
palette: "neutral",
|
|
241
|
+
placement: "bottom-end",
|
|
242
|
+
variant: "solid"
|
|
243
|
+
},
|
|
244
|
+
jsx: [/^Avatar\.Badge$/u],
|
|
245
|
+
staticCss: [{ palette: [...PALETTES] }],
|
|
246
|
+
variants: {
|
|
247
|
+
palette: paletteVariants(),
|
|
248
|
+
/**
|
|
249
|
+
* The corner the badge is pinned to, in the direction of the text.
|
|
250
|
+
*/
|
|
251
|
+
placement: {
|
|
252
|
+
"bottom-end": {
|
|
253
|
+
_rtl: { translate: "-50% 50%" },
|
|
254
|
+
insetBlockEnd: RIM,
|
|
255
|
+
insetInlineEnd: RIM,
|
|
256
|
+
translate: "50% 50%"
|
|
257
|
+
},
|
|
258
|
+
"bottom-start": {
|
|
259
|
+
_rtl: { translate: "50% 50%" },
|
|
260
|
+
insetBlockEnd: RIM,
|
|
261
|
+
insetInlineStart: RIM,
|
|
262
|
+
translate: "-50% 50%"
|
|
263
|
+
},
|
|
264
|
+
"top-end": {
|
|
265
|
+
_rtl: { translate: "-50% -50%" },
|
|
266
|
+
insetBlockStart: RIM,
|
|
267
|
+
insetInlineEnd: RIM,
|
|
268
|
+
translate: "50% -50%"
|
|
269
|
+
},
|
|
270
|
+
"top-start": {
|
|
271
|
+
_rtl: { translate: "50% -50%" },
|
|
272
|
+
insetBlockStart: RIM,
|
|
273
|
+
insetInlineStart: RIM,
|
|
274
|
+
translate: "-50% -50%"
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
variant: flatVariants(["solid", "subtle"])
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
//#endregion
|
|
281
|
+
//#region src/carousel/recipe.ts
|
|
282
|
+
/**
|
|
283
|
+
* Styles a carousel: the slides in a scroller that snaps a page at a time, the triggers that move a
|
|
284
|
+
* page, the dots that pick one, the rotation control and the progress text.
|
|
285
|
+
*
|
|
286
|
+
* @remarks
|
|
287
|
+
* The machine writes the scroller's layout inline, a grid of slides a page wide with scroll
|
|
288
|
+
* snapping, and spaces the slides by its `spacing` option. The root passes that option a custom
|
|
289
|
+
* property the recipe sets to the theme's `gap.md`. A dot is a 24px target around an 8px mark in
|
|
290
|
+
* the palette's `border` role, which measures 3:1 against every surface. The current dot is a
|
|
291
|
+
* 20px pill in the palette's solid, so the page it marks differs in shape as well as in color.
|
|
292
|
+
* With `controls` set to `overlay`, the controls share the slides' grid cell. The triggers are at
|
|
293
|
+
* the sides, the dots on a `bg.panel` pill at the bottom, padded by a dot's 2px focus ring, and
|
|
294
|
+
* the rotation control and the progress text at the top. In a vertical carousel the machine sizes
|
|
295
|
+
* a slide to a share of the scroller's height, so the scroller takes the `landscape` ratio unless
|
|
296
|
+
* `ratio` sets another. A picture that is a slide's child covers the slide: it keeps its own
|
|
297
|
+
* aspect ratio where the slide is as tall as its content, and the slide crops it where the
|
|
298
|
+
* scroller's ratio sets the height.
|
|
299
|
+
*/
|
|
300
|
+
/**
|
|
301
|
+
* Custom property the root passes to the machine as the gap between slides.
|
|
302
|
+
*/
|
|
303
|
+
const GAP = "--carousel-gap";
|
|
304
|
+
/**
|
|
305
|
+
* Selects a part of a carousel that runs top to bottom.
|
|
306
|
+
*/
|
|
307
|
+
const VERTICAL = "&[data-orientation=vertical]";
|
|
308
|
+
/**
|
|
309
|
+
* Corners the slides offer. A fully round slide would clip its picture to an ellipse.
|
|
310
|
+
*/
|
|
311
|
+
const CORNERS = cornerVariants([
|
|
312
|
+
"l1",
|
|
313
|
+
"l2",
|
|
314
|
+
"l3"
|
|
315
|
+
]);
|
|
316
|
+
/**
|
|
317
|
+
* Distance of an overlaid control from the slides' edge.
|
|
318
|
+
*/
|
|
319
|
+
const EDGE = dense("{spacing.gap.sm}");
|
|
320
|
+
/**
|
|
321
|
+
* Sets a `bg.panel` pill behind an overlaid control, so it reads over any photograph.
|
|
322
|
+
*/
|
|
323
|
+
const PILL = {
|
|
324
|
+
background: "bg.panel",
|
|
325
|
+
borderRadius: "full",
|
|
326
|
+
boxShadow: "sm",
|
|
327
|
+
position: "absolute"
|
|
328
|
+
};
|
|
329
|
+
/**
|
|
330
|
+
* Defines the carousel recipe, with the controls under the slides, neutral dots and `l3` slides by
|
|
331
|
+
* default.
|
|
332
|
+
*/
|
|
333
|
+
const recipe$3 = defineSlotRecipe({
|
|
334
|
+
base: {
|
|
335
|
+
autoplayTrigger: { flexShrink: "0" },
|
|
336
|
+
control: {
|
|
337
|
+
alignItems: "center",
|
|
338
|
+
display: "flex",
|
|
339
|
+
gap: dense("{spacing.gap.sm}"),
|
|
340
|
+
justifyContent: "space-between",
|
|
341
|
+
[VERTICAL]: { flexDirection: "column" }
|
|
342
|
+
},
|
|
343
|
+
indicator: {
|
|
344
|
+
_current: {
|
|
345
|
+
_hover: { "&::after": { background: "colorPalette.solid.hover" } },
|
|
346
|
+
"&::after": {
|
|
347
|
+
background: "colorPalette.solid",
|
|
348
|
+
inlineSize: "5"
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
_highContrast: {
|
|
352
|
+
_current: { "&::after": { background: "Highlight" } },
|
|
353
|
+
"&::after": {
|
|
354
|
+
background: "CanvasText",
|
|
355
|
+
forcedColorAdjust: "none"
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
_hover: { "&::after": { background: "colorPalette.border.hover" } },
|
|
359
|
+
"&::after": {
|
|
360
|
+
background: "colorPalette.border",
|
|
361
|
+
blockSize: "2",
|
|
362
|
+
borderRadius: "full",
|
|
363
|
+
content: "''",
|
|
364
|
+
inlineSize: "2",
|
|
365
|
+
transitionDuration: "moderate",
|
|
366
|
+
transitionProperty: "inline-size, background-color"
|
|
367
|
+
},
|
|
368
|
+
alignItems: "center",
|
|
369
|
+
background: "transparent",
|
|
370
|
+
borderRadius: "full",
|
|
371
|
+
borderWidth: "0",
|
|
372
|
+
boxSize: "6",
|
|
373
|
+
cursor: "button",
|
|
374
|
+
display: "inline-flex",
|
|
375
|
+
flexShrink: "0",
|
|
376
|
+
focusRingColor: "colorPalette.focusRing",
|
|
377
|
+
focusVisibleRing: "inside",
|
|
378
|
+
justifyContent: "center",
|
|
379
|
+
padding: "0"
|
|
380
|
+
},
|
|
381
|
+
indicatorGroup: {
|
|
382
|
+
alignItems: "center",
|
|
383
|
+
display: "flex",
|
|
384
|
+
flexWrap: "wrap",
|
|
385
|
+
justifyContent: "center",
|
|
386
|
+
[VERTICAL]: { flexDirection: "column" }
|
|
387
|
+
},
|
|
388
|
+
item: {
|
|
389
|
+
"& > img": {
|
|
390
|
+
blockSize: "full",
|
|
391
|
+
display: "block",
|
|
392
|
+
inlineSize: "full",
|
|
393
|
+
objectFit: "cover"
|
|
394
|
+
},
|
|
395
|
+
overflow: "hidden",
|
|
396
|
+
position: "relative"
|
|
397
|
+
},
|
|
398
|
+
itemGroup: {
|
|
399
|
+
focusRingColor: "colorPalette.focusRing",
|
|
400
|
+
focusVisibleRing: "outside",
|
|
401
|
+
[VERTICAL]: {
|
|
402
|
+
aspectRatio: "landscape",
|
|
403
|
+
flex: "1",
|
|
404
|
+
minInlineSize: "0"
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
nextTrigger: { flexShrink: "0" },
|
|
408
|
+
prevTrigger: { flexShrink: "0" },
|
|
409
|
+
progressText: {
|
|
410
|
+
color: "fg.muted",
|
|
411
|
+
fontVariantNumeric: "tabular-nums",
|
|
412
|
+
textStyle: "label.sm",
|
|
413
|
+
whiteSpace: "nowrap"
|
|
414
|
+
},
|
|
415
|
+
root: {
|
|
416
|
+
[GAP]: dense("{spacing.gap.md}"),
|
|
417
|
+
gap: dense("{spacing.gap.sm}"),
|
|
418
|
+
minInlineSize: "0",
|
|
419
|
+
position: "relative"
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
className: "carousel",
|
|
423
|
+
defaultVariants: {
|
|
424
|
+
controls: "outside",
|
|
425
|
+
palette: "neutral",
|
|
426
|
+
radius: "l3"
|
|
427
|
+
},
|
|
428
|
+
jsx: [/^Carousel(\.\w+)?$/u],
|
|
429
|
+
slots: [
|
|
430
|
+
"root",
|
|
431
|
+
"itemGroup",
|
|
432
|
+
"item",
|
|
433
|
+
"control",
|
|
434
|
+
"nextTrigger",
|
|
435
|
+
"prevTrigger",
|
|
436
|
+
"indicatorGroup",
|
|
437
|
+
"indicator",
|
|
438
|
+
"autoplayTrigger",
|
|
439
|
+
"progressText"
|
|
440
|
+
],
|
|
441
|
+
variants: {
|
|
442
|
+
/**
|
|
443
|
+
* Where the controls go: beside the slides in the root's flow, or over the slides.
|
|
444
|
+
*
|
|
445
|
+
* @remarks
|
|
446
|
+
* Beside the slides, the root is a flex column, a row in a vertical carousel. Over the
|
|
447
|
+
* slides, the root is a grid whose first cell contains both the scroller and the control, so
|
|
448
|
+
* the control covers the slides exactly and any other child takes the next row. The control
|
|
449
|
+
* passes presses through, and each control in it takes them.
|
|
450
|
+
*/
|
|
451
|
+
controls: {
|
|
452
|
+
outside: { root: {
|
|
453
|
+
display: "flex",
|
|
454
|
+
flexDirection: "column",
|
|
455
|
+
[VERTICAL]: { flexDirection: "row" }
|
|
456
|
+
} },
|
|
457
|
+
overlay: {
|
|
458
|
+
autoplayTrigger: {
|
|
459
|
+
insetBlockStart: EDGE,
|
|
460
|
+
insetInlineStart: EDGE,
|
|
461
|
+
position: "absolute"
|
|
462
|
+
},
|
|
463
|
+
control: {
|
|
464
|
+
"& > *": { pointerEvents: "auto" },
|
|
465
|
+
gridArea: "1 / 1 / 2 / 2",
|
|
466
|
+
padding: EDGE,
|
|
467
|
+
pointerEvents: "none",
|
|
468
|
+
position: "relative"
|
|
469
|
+
},
|
|
470
|
+
indicatorGroup: {
|
|
471
|
+
...PILL,
|
|
472
|
+
insetBlockEnd: EDGE,
|
|
473
|
+
left: "50%",
|
|
474
|
+
padding: "0.5",
|
|
475
|
+
translate: "-50% 0",
|
|
476
|
+
[VERTICAL]: {
|
|
477
|
+
insetBlockEnd: "auto",
|
|
478
|
+
insetInlineEnd: EDGE,
|
|
479
|
+
left: "auto",
|
|
480
|
+
top: "50%",
|
|
481
|
+
translate: "0 -50%"
|
|
482
|
+
}
|
|
483
|
+
},
|
|
484
|
+
itemGroup: { gridArea: "1 / 1 / 2 / 2" },
|
|
485
|
+
progressText: {
|
|
486
|
+
...PILL,
|
|
487
|
+
color: "fg",
|
|
488
|
+
insetBlockStart: EDGE,
|
|
489
|
+
insetInlineEnd: EDGE,
|
|
490
|
+
paddingBlock: "1",
|
|
491
|
+
paddingInline: dense("{spacing.gap.sm}")
|
|
492
|
+
},
|
|
493
|
+
root: { display: "grid" }
|
|
494
|
+
}
|
|
495
|
+
},
|
|
496
|
+
/**
|
|
497
|
+
* Palette of the dots.
|
|
498
|
+
*/
|
|
499
|
+
palette: onSlot("indicator", paletteVariants()),
|
|
500
|
+
/**
|
|
501
|
+
* Corner radius token of the slides and of the scroller's focus ring.
|
|
502
|
+
*/
|
|
503
|
+
radius: onSlots({
|
|
504
|
+
item: CORNERS,
|
|
505
|
+
itemGroup: CORNERS
|
|
506
|
+
}),
|
|
507
|
+
/**
|
|
508
|
+
* Aspect ratio token of the scroller, whose height a vertical carousel needs. A horizontal
|
|
509
|
+
* carousel without it is as tall as its slides.
|
|
510
|
+
*/
|
|
511
|
+
ratio: onSlot("itemGroup", ratioVariants())
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
//#endregion
|
|
515
|
+
//#region src/iframe/recipe.ts
|
|
516
|
+
/**
|
|
517
|
+
* Styles an `iframe` as a box of one of the theme's ratios, as wide as its container.
|
|
518
|
+
*
|
|
519
|
+
* @remarks
|
|
520
|
+
* The ratio holds the box's shape before the framed document loads, so nothing below it moves
|
|
521
|
+
* when it does. The box is `bg.panel` with a hairline edge, which bounds a framed page with a
|
|
522
|
+
* white ground on a white page. The recipe sets no focus ring. Firefox and Chromium match no
|
|
523
|
+
* focus pseudo-class on an `iframe` while its document has focus, as the HTML Standard asks for a
|
|
524
|
+
* navigable container.
|
|
525
|
+
*/
|
|
526
|
+
/**
|
|
527
|
+
* Styles a 16:9 frame with the `l3` corners.
|
|
528
|
+
*/
|
|
529
|
+
const recipe$2 = defineRecipe({
|
|
530
|
+
base: {
|
|
531
|
+
background: "bg.panel",
|
|
532
|
+
blockSize: "auto",
|
|
533
|
+
borderColor: "border",
|
|
534
|
+
borderStyle: "solid",
|
|
535
|
+
borderWidth: "hairline",
|
|
536
|
+
display: "block",
|
|
537
|
+
inlineSize: "full",
|
|
538
|
+
maxInlineSize: "full"
|
|
539
|
+
},
|
|
540
|
+
className: "iframe",
|
|
541
|
+
defaultVariants: {
|
|
542
|
+
radius: "l3",
|
|
543
|
+
ratio: "video"
|
|
544
|
+
},
|
|
545
|
+
jsx: [/^Iframe$/u],
|
|
546
|
+
variants: {
|
|
547
|
+
/**
|
|
548
|
+
* Corner radius token. The axis leaves out `full`, because a framed document's content reaches
|
|
549
|
+
* the frame's corners and a fully round corner clips it.
|
|
550
|
+
*/
|
|
551
|
+
radius: cornerVariants([
|
|
552
|
+
"l1",
|
|
553
|
+
"l2",
|
|
554
|
+
"l3"
|
|
555
|
+
]),
|
|
556
|
+
/**
|
|
557
|
+
* Aspect ratio token the frame keeps before and after its document loads.
|
|
558
|
+
*/
|
|
559
|
+
ratio: ratioVariants()
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
//#endregion
|
|
563
|
+
//#region src/image-cropper/recipe.ts
|
|
564
|
+
/**
|
|
565
|
+
* Styles an image cropper: a picture in a viewport, the selection over the part it keeps with the
|
|
566
|
+
* rest dimmed, the handles that resize the selection, and the thirds shown while it moves.
|
|
567
|
+
*
|
|
568
|
+
* @remarks
|
|
569
|
+
* The picture sets the viewport's shape: the image spans the viewport's width at its own ratio,
|
|
570
|
+
* because the machine writes `object-fit: fill` on it and measures the image's box for the crop
|
|
571
|
+
* it exports. The selection's box is the crop. Its line is a `bg.panel` hairline border and a
|
|
572
|
+
* `border.emphasized` inset hairline, so one of them contrasts with any photograph in either
|
|
573
|
+
* mode. The line's middle is the selection's padding edge, where the machine centres every
|
|
574
|
+
* handle. A spread shadow in `bg.backdrop` dims the viewport outside the crop. The focus ring is
|
|
575
|
+
* one color, so a `bg.panel` spread fills the gap under it and the ring contrasts with that band
|
|
576
|
+
* over any photograph. Forced colors drop shadows and paint the border in `CanvasText`, and the
|
|
577
|
+
* handles' dots keep a transparent hairline border for the same reason. A handle is a 24px
|
|
578
|
+
* target, and its visible dot is a pseudo-element inside it. The viewport and the selection take
|
|
579
|
+
* the `drag` cursor, because a press on either moves the crop or pans the picture, and the
|
|
580
|
+
* selection takes `dragging` while it moves.
|
|
581
|
+
*/
|
|
582
|
+
/**
|
|
583
|
+
* Class name of the recipe, from which the binding writes each part's class.
|
|
584
|
+
*/
|
|
585
|
+
const CLASS = "image-cropper";
|
|
586
|
+
/**
|
|
587
|
+
* Paints the inner hairline of the selection's line.
|
|
588
|
+
*/
|
|
589
|
+
const LINE = "inset 0 0 0 {borderWidths.hairline} {colors.border.emphasized}";
|
|
590
|
+
/**
|
|
591
|
+
* Fills the gap between the selection and its focus ring with `bg.panel`, so the ring contrasts
|
|
592
|
+
* with that band over any photograph.
|
|
593
|
+
*/
|
|
594
|
+
const BAND = "0 0 0 {spacing.ring} {colors.bg.panel}";
|
|
595
|
+
/**
|
|
596
|
+
* Dims the viewport outside the selection.
|
|
597
|
+
*/
|
|
598
|
+
const DIM = "0 0 0 100vmax {colors.bg.backdrop}";
|
|
599
|
+
/**
|
|
600
|
+
* Defines the image cropper recipe, with the `l2` corners on the viewport by default.
|
|
601
|
+
*/
|
|
602
|
+
const recipe$1 = defineSlotRecipe({
|
|
603
|
+
base: {
|
|
604
|
+
grid: {
|
|
605
|
+
"&:is([data-dragging], [data-panning])": { opacity: "1" },
|
|
606
|
+
"&[data-axis=horizontal]": { borderBlockWidth: "hairline" },
|
|
607
|
+
"&[data-axis=vertical]": { borderInlineWidth: "hairline" },
|
|
608
|
+
borderColor: "bg.panel",
|
|
609
|
+
borderStyle: "solid",
|
|
610
|
+
borderWidth: "0",
|
|
611
|
+
opacity: "0",
|
|
612
|
+
transitionDuration: "moderate",
|
|
613
|
+
transitionProperty: "opacity"
|
|
614
|
+
},
|
|
615
|
+
handle: {
|
|
616
|
+
"&::after": {
|
|
617
|
+
background: "bg.panel",
|
|
618
|
+
borderColor: "transparent",
|
|
619
|
+
borderRadius: "full",
|
|
620
|
+
borderStyle: "solid",
|
|
621
|
+
borderWidth: "hairline",
|
|
622
|
+
boxShadow: "0 0 0 {borderWidths.hairline} {colors.border.emphasized}",
|
|
623
|
+
boxSize: "2.5",
|
|
624
|
+
boxSizing: "border-box",
|
|
625
|
+
content: "''"
|
|
626
|
+
},
|
|
627
|
+
"&[data-disabled]": { display: "none" },
|
|
628
|
+
alignItems: "center",
|
|
629
|
+
blockSize: "6",
|
|
630
|
+
["&:is([data-position=ne], [data-position=nw], [data-position=se], [data-position=sw])"]: { "&::after": { boxSize: "3" } },
|
|
631
|
+
display: "flex",
|
|
632
|
+
inlineSize: "6",
|
|
633
|
+
justifyContent: "center",
|
|
634
|
+
touchAction: "none"
|
|
635
|
+
},
|
|
636
|
+
image: {
|
|
637
|
+
blockSize: "auto",
|
|
638
|
+
display: "block",
|
|
639
|
+
inlineSize: "full",
|
|
640
|
+
maxInlineSize: "none"
|
|
641
|
+
},
|
|
642
|
+
root: {
|
|
643
|
+
display: "block",
|
|
644
|
+
inlineSize: "full",
|
|
645
|
+
position: "relative"
|
|
646
|
+
},
|
|
647
|
+
selection: {
|
|
648
|
+
_focusVisible: { boxShadow: [
|
|
649
|
+
LINE,
|
|
650
|
+
BAND,
|
|
651
|
+
DIM
|
|
652
|
+
].join(", ") },
|
|
653
|
+
"&:is([data-dragging], [data-panning])": { cursor: "dragging" },
|
|
654
|
+
"&[data-shape=circle]": { borderRadius: "full" },
|
|
655
|
+
borderColor: "bg.panel",
|
|
656
|
+
borderStyle: "solid",
|
|
657
|
+
borderWidth: "hairline",
|
|
658
|
+
boxShadow: [LINE, DIM].join(", "),
|
|
659
|
+
boxSizing: "border-box",
|
|
660
|
+
cursor: "drag",
|
|
661
|
+
focusRingColor: "colorPalette.focusRing",
|
|
662
|
+
focusVisibleRing: "outside"
|
|
663
|
+
},
|
|
664
|
+
viewport: {
|
|
665
|
+
background: "bg.emphasized",
|
|
666
|
+
cursor: "drag",
|
|
667
|
+
display: "block"
|
|
668
|
+
}
|
|
669
|
+
},
|
|
670
|
+
className: CLASS,
|
|
671
|
+
defaultVariants: { radius: "l2" },
|
|
672
|
+
jsx: [/^ImageCropper(\.\w+)?$/u],
|
|
673
|
+
slots: [
|
|
674
|
+
"root",
|
|
675
|
+
"viewport",
|
|
676
|
+
"image",
|
|
677
|
+
"selection",
|
|
678
|
+
"handle",
|
|
679
|
+
"grid"
|
|
680
|
+
],
|
|
681
|
+
variants: {
|
|
682
|
+
/**
|
|
683
|
+
* Corner radius token of the viewport. The axis leaves out `full`, because a fully round
|
|
684
|
+
* viewport clips the selection's corners and the handles on them.
|
|
685
|
+
*/
|
|
686
|
+
radius: onSlot("viewport", cornerVariants([
|
|
687
|
+
"l1",
|
|
688
|
+
"l2",
|
|
689
|
+
"l3"
|
|
690
|
+
])) }
|
|
691
|
+
});
|
|
692
|
+
//#endregion
|
|
693
|
+
//#region src/video/recipe.ts
|
|
694
|
+
/**
|
|
695
|
+
* Styles a video: a block of a theme aspect ratio that keeps its shape before the clip's metadata
|
|
696
|
+
* loads.
|
|
697
|
+
*
|
|
698
|
+
* @remarks
|
|
699
|
+
* A `video` reports its size only once its metadata loads, so without a ratio it is 0 pixels tall
|
|
700
|
+
* and grows mid-load, which moves everything below it. The ratio defaults to `video`, 16:9. The
|
|
701
|
+
* background is `bg.emphasized`, which shows while the poster loads and fills the bars of the
|
|
702
|
+
* `contain` fit. A hairline edge bounds pale footage on a light page. The recipe has no `palette`
|
|
703
|
+
* axis, because the clip fills the element, and no `effect` axis, because a glow in a palette has
|
|
704
|
+
* no relation to the clip.
|
|
705
|
+
*/
|
|
706
|
+
/**
|
|
707
|
+
* Defines the video recipe, which defaults to a 16:9 clip that fills its shape inside `l3` corners.
|
|
708
|
+
*/
|
|
709
|
+
const recipe = defineRecipe({
|
|
710
|
+
base: {
|
|
711
|
+
background: "bg.emphasized",
|
|
712
|
+
blockSize: "auto",
|
|
713
|
+
borderColor: "border",
|
|
714
|
+
borderStyle: "solid",
|
|
715
|
+
borderWidth: "hairline",
|
|
716
|
+
display: "block",
|
|
717
|
+
focusRingColor: "colorPalette.focusRing",
|
|
718
|
+
focusVisibleRing: "outside",
|
|
719
|
+
inlineSize: "full",
|
|
720
|
+
maxInlineSize: "full"
|
|
721
|
+
},
|
|
722
|
+
className: "video",
|
|
723
|
+
defaultVariants: {
|
|
724
|
+
fit: "cover",
|
|
725
|
+
radius: "l3",
|
|
726
|
+
ratio: "video"
|
|
727
|
+
},
|
|
728
|
+
jsx: [/^Video$/u],
|
|
729
|
+
variants: {
|
|
730
|
+
/**
|
|
731
|
+
* Object fit of the clip. `cover` crops it to the shape and `contain` shows the whole clip
|
|
732
|
+
* between bars.
|
|
733
|
+
*/
|
|
734
|
+
fit: {
|
|
735
|
+
contain: { objectFit: "contain" },
|
|
736
|
+
cover: { objectFit: "cover" }
|
|
737
|
+
},
|
|
738
|
+
/**
|
|
739
|
+
* Corner radius token.
|
|
740
|
+
*/
|
|
741
|
+
radius: cornerVariants(),
|
|
742
|
+
/**
|
|
743
|
+
* Aspect ratio token the element keeps before and after the metadata loads.
|
|
744
|
+
*/
|
|
745
|
+
ratio: ratioVariants()
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
//#endregion
|
|
749
|
+
export { recipe$3 as a, recipe$6 as c, GAP as i, recipe$1 as n, recipe$4 as o, recipe$2 as r, recipe$5 as s, recipe as t };
|