@threadlabs/looma 0.33.1 → 0.35.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/components/shared/overlay.js +8 -2
- package/components/shared/surface-material.js +21 -0
- package/components/ui-badge/ui-badge.html +11 -3
- package/components/ui-button/ui-button.html +92 -51
- package/components/ui-card/ui-card.html +50 -6
- package/components/ui-checkbox/ui-checkbox.html +4 -2
- package/components/ui-curve-editor/curve.js +10 -0
- package/components/ui-curve-editor/ui-curve-editor.html +181 -0
- package/components/ui-curve-editor/ui-curve-editor.js +51 -0
- package/components/ui-dialog/ui-dialog.html +64 -1
- package/components/ui-dialog/ui-dialog.js +9 -1
- package/components/ui-icon-button/ui-icon-button.html +97 -31
- package/components/ui-input/ui-input.html +2 -1
- package/components/ui-input-group/ui-input-group.html +4 -1
- package/components/ui-light-editor/ui-light-editor.html +45 -0
- package/components/ui-light-editor/ui-light-editor.js +53 -0
- package/components/ui-menu/ui-menu.html +62 -0
- package/components/ui-menu/ui-menu.js +1 -1
- package/components/ui-number-input/arithmetic.js +47 -0
- package/components/ui-number-input/ui-number-input.html +63 -0
- package/components/ui-number-input/ui-number-input.js +47 -0
- package/components/ui-popover/ui-popover.html +66 -1
- package/components/ui-popover/ui-popover.js +1 -1
- package/components/ui-select/ui-select.html +9 -7
- package/components/ui-slider/ui-slider.html +84 -0
- package/components/ui-slider/ui-slider.js +29 -0
- package/components/ui-switch/ui-switch.html +3 -0
- package/components/ui-tabs/ui-tabs.html +6 -1
- package/components/ui-text/ui-text.html +31 -6
- package/components/ui-textarea/ui-textarea.html +2 -1
- package/components/ui-tooltip/ui-tooltip.html +60 -0
- package/components/ui-tooltip/ui-tooltip.js +1 -1
- package/dist/index.js +47 -43
- package/docs/README.md +59 -5
- package/docs/card.html +16 -2
- package/docs/card.js +17 -2
- package/docs/catalog.json +1640 -108
- package/docs/customizer-render.mjs +50 -11
- package/docs/customizer.js +137 -24
- package/docs/documentation.css +9 -1
- package/docs/index.d.ts +8 -3
- package/docs/index.mjs +1 -1
- package/docs/preview.js +2 -0
- package/docs/render.mjs +16 -7
- package/docs/site.mjs +78 -27
- package/docs/theme-session.mjs +53 -0
- package/docs/theme.mjs +189 -18
- package/docs/value-editors.mjs +33 -0
- package/package.json +2 -2
- package/styles/ui-badge.css +11 -6
- package/styles/ui-button.css +74 -49
- package/styles/ui-card.css +51 -4
- package/styles/ui-checkbox.css +4 -2
- package/styles/ui-curve-editor.css +109 -0
- package/styles/ui-dialog.css +136 -0
- package/styles/ui-icon-button.css +85 -28
- package/styles/ui-input-group.css +4 -1
- package/styles/ui-input.css +2 -1
- package/styles/ui-light-editor.css +28 -0
- package/styles/ui-menu.css +74 -0
- package/styles/ui-number-input.css +34 -0
- package/styles/ui-popover.css +76 -0
- package/styles/ui-select.css +9 -7
- package/styles/ui-slider.css +53 -0
- package/styles/ui-switch.css +3 -0
- package/styles/ui-tabs.css +6 -1
- package/styles/ui-text.css +23 -1
- package/styles/ui-textarea.css +2 -1
- package/styles/ui-tooltip.css +72 -0
- package/theme-light.css +1 -0
- package/theme-system.css +69 -17
- package/tokens.css +12 -2
- package/vanilla/UiButton.d.ts +1 -0
- package/vanilla/UiButton.js +195 -130
- package/vanilla/UiCard.d.ts +1 -0
- package/vanilla/UiCard.js +12 -12
- package/vanilla/UiCurveEditor.d.ts +28 -0
- package/vanilla/UiCurveEditor.js +134 -0
- package/vanilla/UiIconButton.d.ts +2 -1
- package/vanilla/UiIconButton.js +58 -31
- package/vanilla/UiLightEditor.d.ts +29 -0
- package/vanilla/UiLightEditor.js +102 -0
- package/vanilla/UiNumberInput.d.ts +33 -0
- package/vanilla/UiNumberInput.js +86 -0
- package/vanilla/UiSlider.d.ts +33 -0
- package/vanilla/UiSlider.js +125 -0
- package/vanilla/UiText.d.ts +2 -2
- package/vanilla/UiText.js +45 -11
- package/vanilla/index.js +4 -0
- package/vue/UiBadge.vue +29 -4
- package/vue/UiButton.d.ts +6 -0
- package/vue/UiButton.vue +188 -60
- package/vue/UiCard.d.ts +6 -0
- package/vue/UiCard.vue +80 -6
- package/vue/UiCheckbox.vue +4 -2
- package/vue/UiCurveEditor.d.ts +53 -0
- package/vue/UiCurveEditor.js +2 -0
- package/vue/UiCurveEditor.vue +427 -0
- package/vue/UiDialog.vue +181 -1
- package/vue/UiIconButton.d.ts +8 -2
- package/vue/UiIconButton.vue +190 -45
- package/vue/UiInput.vue +2 -1
- package/vue/UiInputGroup.vue +4 -1
- package/vue/UiLightEditor.d.ts +75 -0
- package/vue/UiLightEditor.js +2 -0
- package/vue/UiLightEditor.vue +402 -0
- package/vue/UiMenu.vue +91 -0
- package/vue/UiNumberInput.d.ts +83 -0
- package/vue/UiNumberInput.js +2 -0
- package/vue/UiNumberInput.vue +399 -0
- package/vue/UiPopover.vue +94 -1
- package/vue/UiSelect.vue +16 -10
- package/vue/UiSlider.d.ts +83 -0
- package/vue/UiSlider.js +2 -0
- package/vue/UiSlider.vue +412 -0
- package/vue/UiSwitch.vue +3 -0
- package/vue/UiTabs.vue +6 -1
- package/vue/UiText.d.ts +7 -5
- package/vue/UiText.vue +104 -22
- package/vue/UiTextarea.vue +2 -1
- package/vue/UiTooltip.vue +89 -0
- package/vue/chunks/UiActionBar.js +1 -1
- package/vue/chunks/UiAffordanceScope.js +2 -2
- package/vue/chunks/UiAvatar.js +2 -2
- package/vue/chunks/UiAvatarGroup.js +2 -2
- package/vue/chunks/UiBadge.js +3 -3
- package/vue/chunks/UiBreadcrumbItem.js +2 -2
- package/vue/chunks/UiBreadcrumbs.js +2 -2
- package/vue/chunks/UiButton.js +94 -11
- package/vue/chunks/UiCallout.js +2 -2
- package/vue/chunks/UiCard.js +34 -5
- package/vue/chunks/UiCheckbox.js +3 -3
- package/vue/chunks/UiCluster.js +2 -2
- package/vue/chunks/UiColorPicker.js +2 -2
- package/vue/chunks/UiCombobox.js +2 -2
- package/vue/chunks/UiContainer.js +2 -2
- package/vue/chunks/UiContextMenu.js +2 -2
- package/vue/chunks/UiCover.js +2 -2
- package/vue/chunks/UiCurveEditor.js +413 -0
- package/vue/chunks/UiDescriptionItem.js +2 -2
- package/vue/chunks/UiDescriptionList.js +2 -2
- package/vue/chunks/UiDialog.js +4 -4
- package/vue/chunks/UiDisclosure.js +2 -2
- package/vue/chunks/UiEditable.js +2 -2
- package/vue/chunks/UiEditorBlockActions.js +2 -2
- package/vue/chunks/UiEditorChipEditor.js +2 -2
- package/vue/chunks/UiEditorCodeLanguage.js +2 -2
- package/vue/chunks/UiEditorFootnoteEditor.js +2 -2
- package/vue/chunks/UiEditorImageToolbar.js +3 -3
- package/vue/chunks/UiEditorInsertTableGrid.js +2 -2
- package/vue/chunks/UiEditorLinkCard.js +3 -3
- package/vue/chunks/UiEditorLinkForm.js +3 -3
- package/vue/chunks/UiEditorMentionMenu.js +2 -2
- package/vue/chunks/UiEditorMentionMenuItem.js +2 -2
- package/vue/chunks/UiEditorSlashMenu.js +2 -2
- package/vue/chunks/UiEditorSlashMenuGroup.js +2 -2
- package/vue/chunks/UiEditorSlashMenuItem.js +2 -2
- package/vue/chunks/UiEditorSurface.js +2 -2
- package/vue/chunks/UiEditorTableContextMenu.js +2 -2
- package/vue/chunks/UiEditorTableOverlay.js +2 -2
- package/vue/chunks/UiEditorTableToolbar.js +2 -2
- package/vue/chunks/UiEditorToolbar.js +2 -2
- package/vue/chunks/UiFloatingActionButton.js +2 -2
- package/vue/chunks/UiFormField.js +2 -2
- package/vue/chunks/UiGrid.js +2 -2
- package/vue/chunks/UiIcon.js +2 -2
- package/vue/chunks/UiIconButton.js +77 -11
- package/vue/chunks/UiImage.js +2 -2
- package/vue/chunks/UiInput.js +3 -3
- package/vue/chunks/UiInputGroup.js +3 -3
- package/vue/chunks/UiLightEditor.js +347 -0
- package/vue/chunks/UiList.js +2 -2
- package/vue/chunks/UiListItem.js +2 -2
- package/vue/chunks/UiListbox.js +2 -2
- package/vue/chunks/UiMenu.js +3 -3
- package/vue/chunks/UiMenuGroup.js +2 -2
- package/vue/chunks/UiMenuItem.js +2 -2
- package/vue/chunks/UiMeter.js +2 -2
- package/vue/chunks/UiNavItem.js +2 -2
- package/vue/chunks/UiNumberInput.js +403 -0
- package/vue/chunks/UiPageHeader.js +2 -2
- package/vue/chunks/UiPopover.js +4 -4
- package/vue/chunks/UiRadio.js +2 -2
- package/vue/chunks/UiRadioGroup.js +2 -2
- package/vue/chunks/UiReel.js +2 -2
- package/vue/chunks/UiScrollArea.js +2 -2
- package/vue/chunks/UiSearchResultRow.js +2 -2
- package/vue/chunks/UiSearchShell.js +2 -2
- package/vue/chunks/UiSection.js +2 -2
- package/vue/chunks/UiSelect.js +3 -3
- package/vue/chunks/UiSeparator.js +2 -2
- package/vue/chunks/UiSidebar.js +2 -2
- package/vue/chunks/UiSlider.js +396 -0
- package/vue/chunks/UiSpinner.js +2 -2
- package/vue/chunks/UiStack.js +2 -2
- package/vue/chunks/UiStatusMessage.js +2 -2
- package/vue/chunks/UiSwatch.js +2 -2
- package/vue/chunks/UiSwitch.js +3 -3
- package/vue/chunks/UiSwitcher.js +2 -2
- package/vue/chunks/UiTable.js +2 -2
- package/vue/chunks/UiTabs.js +3 -3
- package/vue/chunks/UiText.js +53 -12
- package/vue/chunks/UiTextarea.js +3 -3
- package/vue/chunks/UiToast.js +2 -2
- package/vue/chunks/UiToastRegion.js +2 -2
- package/vue/chunks/UiTooltip.js +3 -3
- package/vue/chunks/UiTopBar.js +1 -1
- package/vue/chunks/UiTree.js +2 -2
- package/vue/chunks/UiTreeItem.js +2 -2
- package/vue/chunks/_plugin-vue_export-helper.js +68 -2
- package/vue/chunks/props.js +24 -1
- package/vue/components.css +3391 -2139
- package/vue/editor/index.js +623 -211
- package/vue/index.d.ts +4 -0
- package/vue/index.js +6 -2
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { updateSurfaceMaterial } from "./surface-material.js";
|
|
2
|
+
|
|
1
3
|
const dialogInvokers = new WeakMap();
|
|
2
4
|
|
|
3
5
|
function stateFor(document) {
|
|
@@ -345,7 +347,10 @@ export function createViewportSurface(surface, options = {}) {
|
|
|
345
347
|
let frame = null;
|
|
346
348
|
const position = () => {
|
|
347
349
|
frame = null;
|
|
348
|
-
if (stop)
|
|
350
|
+
if (stop) {
|
|
351
|
+
if (options.materialRole) updateSurfaceMaterial(surface, options.materialRole);
|
|
352
|
+
options.position?.();
|
|
353
|
+
}
|
|
349
354
|
};
|
|
350
355
|
const refresh = () => {
|
|
351
356
|
if (stop && frame === null) frame = owner.requestAnimationFrame(position);
|
|
@@ -378,6 +383,7 @@ export function createViewportSurface(surface, options = {}) {
|
|
|
378
383
|
show() {
|
|
379
384
|
if (destroyed) return;
|
|
380
385
|
show(surface);
|
|
386
|
+
if (options.materialRole) updateSurfaceMaterial(surface, options.materialRole);
|
|
381
387
|
stop ??= observeOverlayViewport(surface.ownerDocument, position);
|
|
382
388
|
options.position?.();
|
|
383
389
|
if (!sizeObserver && owner.ResizeObserver) {
|
|
@@ -435,7 +441,7 @@ export function createAnchoredSurface(surface, options = {}) {
|
|
|
435
441
|
surface.style.position = "fixed";
|
|
436
442
|
surface.style.setProperty("margin", "0", "important");
|
|
437
443
|
// `bare` resets the browser's popover chrome on a surface that has none of its own.
|
|
438
|
-
const presentation = createViewportSurface(surface, { position, bare: options.bare });
|
|
444
|
+
const presentation = createViewportSurface(surface, { position, bare: options.bare, materialRole: options.materialRole });
|
|
439
445
|
// Resize and scroll events do not report an ancestor's transition or layout shift.
|
|
440
446
|
// While visible, sample the live anchor and only re-position when its bounds change.
|
|
441
447
|
const trackAnchor = () => {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Purpose has greater influence than size: even a large menu stays lighter than a dialog.
|
|
2
|
+
const roleWeight = Object.freeze({ tooltip: 0.08, menu: 0.28, popover: 0.55, dialog: 1, panel: 0.65 });
|
|
3
|
+
|
|
4
|
+
/** Derive a bounded material scale from a surface's purpose and rendered border box. */
|
|
5
|
+
export function surfaceMaterialScale(role, width, height) {
|
|
6
|
+
if (!Object.hasOwn(roleWeight, role)) throw new Error(`Unknown surface material role: ${role}`);
|
|
7
|
+
const measured = [width, height].every(value => Number.isFinite(value) && value > 0);
|
|
8
|
+
const size = measured ? Math.max(0.8, Math.min(1.35, Math.sqrt(width * height / (320 * 240)))) : 1;
|
|
9
|
+
return Math.round(roleWeight[role] * size * 1000) / 1000;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Update component-owned material geometry; a tolerance prevents its rim feeding tiny resize changes back into itself. */
|
|
13
|
+
export function updateSurfaceMaterial(element, role) {
|
|
14
|
+
const width = element.offsetWidth;
|
|
15
|
+
const height = element.offsetHeight;
|
|
16
|
+
const next = surfaceMaterialScale(role, width, height);
|
|
17
|
+
const previous = Number.parseFloat(element.style.getPropertyValue("--_ui-material-scale"));
|
|
18
|
+
if (!Number.isFinite(previous) || Math.abs(next - previous) >= Math.max(0.002, Math.abs(previous) * 0.04)) {
|
|
19
|
+
element.style.setProperty("--_ui-material-scale", String(next));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -325,10 +325,18 @@
|
|
|
325
325
|
box-shadow: none;
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
-
/* Neutral reverses a subtle surface/ink pair rather than a strong tone. Keep its original
|
|
329
|
-
surface ink: moving it toward dark-theme primary text would reduce overlay contrast. */
|
|
330
328
|
:host([variant="inverse"][tone="neutral"]) {
|
|
331
|
-
|
|
329
|
+
--_badge-outline-surface: var(--ui-badge-surface, var(--_badge-color-surface, color-mix(in srgb, var(--ui-surface-raised) 88%, transparent)));
|
|
330
|
+
color: var(--ui-badge-text, var(--_badge-color-text, var(--ui-text-primary)));
|
|
331
|
+
}
|
|
332
|
+
:host([variant="inverse"][tone="neutral"]:not([shape="circle"])) {
|
|
333
|
+
border-color: var(--ui-badge-border, var(--_badge-color-border, var(--_badge-outline-surface)));
|
|
334
|
+
}
|
|
335
|
+
:host([variant="inverse"][tone="neutral"][shape="circle"]) {
|
|
336
|
+
--_badge-outline-surface: var(--ui-badge-surface, var(--_badge-color-surface, light-dark(
|
|
337
|
+
color-mix(in srgb, var(--ui-surface-raised) var(--ui-badge-circle-opacity, 66%), transparent),
|
|
338
|
+
color-mix(in srgb, var(--ui-surface-raised) var(--ui-badge-circle-opacity, 56%), transparent)
|
|
339
|
+
)));
|
|
332
340
|
}
|
|
333
341
|
|
|
334
342
|
:host([variant="outline"]) {
|
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
<link rel="component" href="../ui-icon/ui-icon.html">
|
|
3
3
|
<template component="ui-button" module="./ui-button.js">
|
|
4
4
|
<defs>
|
|
5
|
+
<prop name="preset" type="keyword" values="none, primary, standard, quiet, destructive" default="none"
|
|
6
|
+
>A shortcut that sets appearance and tone together: primary is solid accent, standard is solid neutral, quiet is ghost neutral, and destructive is solid
|
|
7
|
+
danger. A named preset takes precedence over variant and tone. Leave it at none to choose variant and tone independently.</prop>
|
|
8
|
+
<computed name="appearance" from="{ none: $variant, primary: 'solid', standard: 'solid', quiet: 'ghost', destructive: 'solid' }[$preset]"></computed>
|
|
9
|
+
<computed
|
|
10
|
+
name="colorTone"
|
|
11
|
+
from="{ none: { true: 'danger', false: $tone }[concat($variant = 'danger')], primary: 'accent', standard: 'neutral', quiet: 'neutral', destructive: 'danger' }[$preset]"
|
|
12
|
+
></computed>
|
|
5
13
|
<prop name="variant" type="keyword" values="outline, solid, danger, ghost, link, card, field" default="outline"
|
|
6
14
|
>Emphasis: how loudly the button asks. solid fills with its tone, outline draws its tone at the edge over a wash of it, ghost carries only text until it is
|
|
7
15
|
hovered, and link is an inline text action that reads as a link. danger is solid in the danger tone, kept for the call sites that name it. card uses the
|
|
@@ -42,7 +50,9 @@
|
|
|
42
50
|
</defs>
|
|
43
51
|
<template $match>
|
|
44
52
|
<a
|
|
45
|
-
|
|
53
|
+
from:data-appearance="$appearance"
|
|
54
|
+
from:data-tone="$colorTone"
|
|
55
|
+
$when="$appearance = 'card' and $as = 'a'"
|
|
46
56
|
from:href="{ true: null, false: $href }[concat($disabled or $loading or $pending)]"
|
|
47
57
|
from:target="$target"
|
|
48
58
|
from:rel="$rel"
|
|
@@ -57,7 +67,9 @@
|
|
|
57
67
|
<span class="card-action"><slot name="action"><ui-icon name="chevron-right" aria-hidden="true"></ui-icon></slot></span>
|
|
58
68
|
</a>
|
|
59
69
|
<button
|
|
60
|
-
|
|
70
|
+
from:data-appearance="$appearance"
|
|
71
|
+
from:data-tone="$colorTone"
|
|
72
|
+
$when="$appearance = 'card'"
|
|
61
73
|
from:type="$type"
|
|
62
74
|
from:disabled="$disabled"
|
|
63
75
|
from:aria-disabled="{ true: 'true', false: null }[concat($loading or $pending)]"
|
|
@@ -70,6 +82,8 @@
|
|
|
70
82
|
<span class="card-action"><slot name="action"><ui-icon name="chevron-right" aria-hidden="true"></ui-icon></slot></span>
|
|
71
83
|
</button>
|
|
72
84
|
<a
|
|
85
|
+
from:data-appearance="$appearance"
|
|
86
|
+
from:data-tone="$colorTone"
|
|
73
87
|
$when="$as = 'a'"
|
|
74
88
|
from:href="{ true: null, false: $href }[concat($disabled or $loading or $pending)]"
|
|
75
89
|
from:target="$target"
|
|
@@ -83,6 +97,8 @@
|
|
|
83
97
|
<span class="badge"><slot name="badge"></slot></span>
|
|
84
98
|
</a>
|
|
85
99
|
<button
|
|
100
|
+
from:data-appearance="$appearance"
|
|
101
|
+
from:data-tone="$colorTone"
|
|
86
102
|
$else
|
|
87
103
|
from:type="$type"
|
|
88
104
|
from:disabled="$disabled"
|
|
@@ -172,7 +188,7 @@
|
|
|
172
188
|
:host {
|
|
173
189
|
/* Optional inherited action-group values keep text and icon actions under the same light.
|
|
174
190
|
One fine top/start highlight follows the bottom/end lowlight at a joined action seam.
|
|
175
|
-
|
|
191
|
+
The shared theme supplies reflections and glow independently of the resting lift. */
|
|
176
192
|
--_ui-action-start: 1px;
|
|
177
193
|
--_ui-action-edge: var(
|
|
178
194
|
--ui-action-edge,
|
|
@@ -180,6 +196,8 @@
|
|
|
180
196
|
inset calc(-1 * var(--_ui-action-start)) -1px 0 light-dark(rgb(0 0 0 / 0.08), rgb(0 0 0 / 0.08))
|
|
181
197
|
);
|
|
182
198
|
--_ui-action-shadow: var(--_ui-action-edge), 0 1px 2px light-dark(rgb(0 0 0 / 0.10), rgb(0 0 0 / 0.20));
|
|
199
|
+
--_tone-highlight: var(--ui-accent-highlight, none);
|
|
200
|
+
--_tone-glow: var(--ui-accent-glow, 0 0 transparent);
|
|
183
201
|
--_hue: var(--ui-accent);
|
|
184
202
|
--_hue-hover: var(--ui-accent-hover);
|
|
185
203
|
--_hue-active: var(--ui-accent-active);
|
|
@@ -230,8 +248,8 @@
|
|
|
230
248
|
white-space: var(--ui-button-white-space, inherit);
|
|
231
249
|
line-height: var(--ui-line-height-tight);
|
|
232
250
|
padding: var(--ui-control-inset-block-md, var(--ui-space-2)) var(--ui-action-inset-md, var(--ui-space-3)) !important;
|
|
233
|
-
background-image: none;
|
|
234
|
-
box-shadow: var(--ui-button-shadow, var(--ui-action-shadow, var(--_ui-action-shadow)));
|
|
251
|
+
background-image: var(--ui-surface-highlight, none);
|
|
252
|
+
box-shadow: var(--ui-button-shadow, var(--ui-action-shadow, var(--_ui-action-shadow))), var(--_tone-glow);
|
|
235
253
|
cursor: pointer;
|
|
236
254
|
transition:
|
|
237
255
|
scale var(--ui-motion-press, var(--ui-motion-fast)) var(--ui-motion-ease),
|
|
@@ -255,7 +273,7 @@
|
|
|
255
273
|
|
|
256
274
|
/* A count is an adornment, not another line of label text. Keep the standard control height
|
|
257
275
|
with enough inset for a 24px mark; large content can still grow the button naturally. */
|
|
258
|
-
:host:not([
|
|
276
|
+
:host:not([data-appearance="link"]):has(.badge:not(:empty)) {
|
|
259
277
|
padding-block: var(--ui-space-0-5) !important;
|
|
260
278
|
}
|
|
261
279
|
|
|
@@ -300,7 +318,7 @@
|
|
|
300
318
|
|
|
301
319
|
/* Card actions inherit Button's palette and behavior; only their shared layout changes.
|
|
302
320
|
The icon may contain any decorative composition. Empty icons remove their column. */
|
|
303
|
-
:host
|
|
321
|
+
:host[data-appearance="card"] {
|
|
304
322
|
display: grid !important;
|
|
305
323
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
306
324
|
align-content: center !important;
|
|
@@ -313,10 +331,10 @@
|
|
|
313
331
|
box-shadow: none;
|
|
314
332
|
background-image: none;
|
|
315
333
|
}
|
|
316
|
-
:host
|
|
334
|
+
:host[data-appearance="card"][size="lg"] {
|
|
317
335
|
min-block-size: max(var(--ui-control-min-block-size), var(--ui-control-size-lg));
|
|
318
336
|
}
|
|
319
|
-
:host
|
|
337
|
+
:host[data-appearance="card"]:has(.card-icon:empty) {
|
|
320
338
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
321
339
|
}
|
|
322
340
|
.card-icon {
|
|
@@ -343,11 +361,11 @@
|
|
|
343
361
|
}
|
|
344
362
|
|
|
345
363
|
/* A neutral card encloses content quietly; ordinary outlines keep their action-tone edge. */
|
|
346
|
-
:host
|
|
364
|
+
:host[data-appearance="card"][data-tone="neutral"] {
|
|
347
365
|
border-color: var(--ui-button-border, var(--ui-border));
|
|
348
366
|
}
|
|
349
367
|
|
|
350
|
-
:host
|
|
368
|
+
:host[data-appearance="card"][data-tone="neutral"]:hover:not(:disabled, [aria-disabled="true"]) {
|
|
351
369
|
border-color: var(--ui-button-hover-border, var(--ui-border-strong));
|
|
352
370
|
}
|
|
353
371
|
|
|
@@ -359,19 +377,20 @@
|
|
|
359
377
|
box-shadow: var(--ui-pressed);
|
|
360
378
|
}
|
|
361
379
|
|
|
362
|
-
:host
|
|
380
|
+
:host[data-appearance="solid"], :host[data-appearance="danger"] {
|
|
363
381
|
border-color: var(--_tone);
|
|
364
382
|
background-color: var(--_tone);
|
|
365
383
|
color: var(--_on-tone);
|
|
366
|
-
|
|
384
|
+
background-image: var(--_tone-highlight);
|
|
385
|
+
box-shadow: var(--ui-button-solid-shadow, var(--ui-action-shadow, var(--_ui-action-shadow))), var(--_tone-glow);
|
|
367
386
|
}
|
|
368
387
|
|
|
369
|
-
:is(:host
|
|
388
|
+
:is(:host[data-appearance="solid"], :host[data-appearance="danger"]):hover:not(:disabled, [aria-disabled="true"]) {
|
|
370
389
|
background-color: var(--_tone-hover);
|
|
371
390
|
border-color: var(--_tone-hover);
|
|
372
391
|
}
|
|
373
392
|
|
|
374
|
-
:is(:host
|
|
393
|
+
:is(:host[data-appearance="solid"], :host[data-appearance="danger"]):active:not(:disabled, [aria-disabled="true"]) {
|
|
375
394
|
background-color: var(--_tone-active);
|
|
376
395
|
border-color: var(--_tone-active);
|
|
377
396
|
background-image: none;
|
|
@@ -380,7 +399,9 @@
|
|
|
380
399
|
|
|
381
400
|
/* Tone is the colour, variant is the volume. Every pairing works: a danger outline is a
|
|
382
401
|
destructive secondary action, not a special case. */
|
|
383
|
-
:host
|
|
402
|
+
:host[data-tone="neutral"] {
|
|
403
|
+
--_tone-highlight: var(--ui-neutral-highlight, none);
|
|
404
|
+
--_tone-glow: var(--ui-neutral-glow, 0 0 transparent);
|
|
384
405
|
--_hue: var(--ui-text);
|
|
385
406
|
--_hue-hover: var(--ui-text);
|
|
386
407
|
--_hue-active: var(--ui-text);
|
|
@@ -388,40 +409,60 @@
|
|
|
388
409
|
--_on-hue: var(--ui-surface);
|
|
389
410
|
}
|
|
390
411
|
|
|
391
|
-
:host
|
|
412
|
+
:host[data-tone="neutral"][data-appearance="solid"] {
|
|
413
|
+
border-color: var(--ui-button-border, var(--ui-control-border));
|
|
414
|
+
background-color: var(--ui-button-surface, var(--ui-neutral-surface));
|
|
415
|
+
color: var(--ui-button-text, var(--ui-text));
|
|
416
|
+
}
|
|
417
|
+
:host[data-tone="neutral"][data-appearance="solid"]:hover:not(:disabled, [aria-disabled="true"]) {
|
|
418
|
+
background-color: var(--ui-button-hover-surface, var(--ui-neutral-hover));
|
|
419
|
+
}
|
|
420
|
+
:host[data-tone="neutral"][data-appearance="solid"]:active:not(:disabled, [aria-disabled="true"]) {
|
|
421
|
+
background-color: var(--ui-button-active-surface, var(--ui-neutral-active));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
:host[data-tone="danger"], :host[data-appearance="danger"] {
|
|
425
|
+
--_tone-highlight: var(--ui-danger-highlight, none);
|
|
426
|
+
--_tone-glow: var(--ui-danger-glow, 0 0 transparent);
|
|
392
427
|
--_hue: var(--ui-danger);
|
|
393
428
|
--_hue-hover: var(--ui-danger-hover);
|
|
394
|
-
--_hue-active: var(--ui-danger-hover);
|
|
395
|
-
--_hue-text: var(--ui-danger-
|
|
429
|
+
--_hue-active: var(--ui-danger-active, var(--ui-danger-hover));
|
|
430
|
+
--_hue-text: var(--ui-danger-subtle-text);
|
|
396
431
|
--_on-hue: var(--ui-on-danger);
|
|
397
432
|
}
|
|
398
433
|
|
|
399
|
-
:host
|
|
434
|
+
:host[data-tone="success"] {
|
|
435
|
+
--_tone-highlight: var(--ui-success-highlight, none);
|
|
436
|
+
--_tone-glow: var(--ui-success-glow, 0 0 transparent);
|
|
400
437
|
--_hue: var(--ui-success);
|
|
401
|
-
--_hue-hover:
|
|
402
|
-
--_hue-active:
|
|
403
|
-
--_hue-text:
|
|
404
|
-
--_on-hue: var(--ui-
|
|
438
|
+
--_hue-hover: var(--ui-success-hover);
|
|
439
|
+
--_hue-active: var(--ui-success-active);
|
|
440
|
+
--_hue-text: var(--ui-success-subtle-text);
|
|
441
|
+
--_on-hue: var(--ui-success-on-strong);
|
|
405
442
|
}
|
|
406
443
|
|
|
407
|
-
:host
|
|
444
|
+
:host[data-tone="warning"] {
|
|
445
|
+
--_tone-highlight: var(--ui-warning-highlight, none);
|
|
446
|
+
--_tone-glow: var(--ui-warning-glow, 0 0 transparent);
|
|
408
447
|
--_hue: var(--ui-warning);
|
|
409
|
-
--_hue-hover:
|
|
410
|
-
--_hue-active:
|
|
411
|
-
--_hue-text:
|
|
412
|
-
--_on-hue: var(--ui-
|
|
448
|
+
--_hue-hover: var(--ui-warning-hover);
|
|
449
|
+
--_hue-active: var(--ui-warning-active);
|
|
450
|
+
--_hue-text: var(--ui-warning-subtle-text);
|
|
451
|
+
--_on-hue: var(--ui-warning-on-strong);
|
|
413
452
|
}
|
|
414
453
|
|
|
415
|
-
:host
|
|
454
|
+
:host[data-tone="info"] {
|
|
455
|
+
--_tone-highlight: var(--ui-info-highlight, none);
|
|
456
|
+
--_tone-glow: var(--ui-info-glow, 0 0 transparent);
|
|
416
457
|
--_hue: var(--ui-info);
|
|
417
|
-
--_hue-hover:
|
|
418
|
-
--_hue-active:
|
|
419
|
-
--_hue-text:
|
|
420
|
-
--_on-hue: var(--ui-
|
|
458
|
+
--_hue-hover: var(--ui-info-hover);
|
|
459
|
+
--_hue-active: var(--ui-info-active);
|
|
460
|
+
--_hue-text: var(--ui-info-subtle-text);
|
|
461
|
+
--_on-hue: var(--ui-info-on-strong);
|
|
421
462
|
}
|
|
422
463
|
|
|
423
464
|
/* A value picker is a field, with Select's quiet edge and resting lift. */
|
|
424
|
-
:host
|
|
465
|
+
:host[data-appearance="field"] {
|
|
425
466
|
--_ui-field-shadow: var(--ui-shadow-sm);
|
|
426
467
|
border-color: var(--ui-button-border, var(--ui-control-border, var(--ui-border-strong)));
|
|
427
468
|
border-radius: var(--ui-button-radius, var(--ui-field-radius, var(--ui-radius-md)));
|
|
@@ -431,23 +472,23 @@
|
|
|
431
472
|
box-shadow: var(--ui-button-shadow, var(--_ui-field-shadow));
|
|
432
473
|
}
|
|
433
474
|
|
|
434
|
-
:host
|
|
475
|
+
:host[data-appearance="field"]:hover:not(:disabled, [aria-disabled="true"]) {
|
|
435
476
|
border-color: var(--ui-button-hover-border, var(--ui-control-border-hover, var(--ui-text-secondary)));
|
|
436
477
|
background-color: var(--ui-button-hover-surface, var(--ui-control-surface, var(--ui-surface-elevated)));
|
|
437
478
|
}
|
|
438
479
|
|
|
439
|
-
:host
|
|
480
|
+
:host[data-appearance="field"]:is(:active, [aria-expanded="true"]):not(:disabled, [aria-disabled="true"]) {
|
|
440
481
|
--_ui-field-shadow: var(--ui-pressed);
|
|
441
482
|
}
|
|
442
483
|
|
|
443
|
-
:host
|
|
484
|
+
:host[data-appearance="field"]:focus-visible {
|
|
444
485
|
border-color: var(--ui-control-focus, var(--ui-accent-solid));
|
|
445
486
|
box-shadow:
|
|
446
487
|
var(--ui-button-shadow, var(--_ui-field-shadow)),
|
|
447
488
|
var(--ui-control-focus-halo-shadow);
|
|
448
489
|
}
|
|
449
490
|
|
|
450
|
-
:host
|
|
491
|
+
:host[data-appearance="ghost"] {
|
|
451
492
|
border-color: transparent;
|
|
452
493
|
background: transparent;
|
|
453
494
|
box-shadow: none;
|
|
@@ -455,14 +496,14 @@
|
|
|
455
496
|
}
|
|
456
497
|
|
|
457
498
|
/* Tints derive from the text colour, not a surface token, so hover and press show on any background. */
|
|
458
|
-
:host
|
|
499
|
+
:host[data-appearance="ghost"]:hover:not(:disabled, [aria-disabled="true"]) {
|
|
459
500
|
background-color: var(--ui-button-ghost-hover-surface, oklch(from var(--_tone) l c h / 10%));
|
|
460
501
|
border-color: transparent;
|
|
461
502
|
}
|
|
462
503
|
|
|
463
504
|
/* Pressed, or holding its menu open, a ghost button sinks slightly: a light surface with a faint
|
|
464
505
|
edge and a soft inner shadow. Lighter than hover, so an open menu's trigger never reads as heavy. */
|
|
465
|
-
:host
|
|
506
|
+
:host[data-appearance="ghost"]:is(:active, [aria-expanded="true"]):not(:disabled, [aria-disabled="true"]) {
|
|
466
507
|
background-color: var(--ui-button-ghost-active-surface, color-mix(in oklch, var(--ui-surface), var(--_tone) 3%));
|
|
467
508
|
border-color: transparent;
|
|
468
509
|
box-shadow: inset 0 0 0 1px oklch(from var(--_tone) l c h / 10%), inset 0 1px 2px oklch(from var(--_tone) l c h / 8%);
|
|
@@ -476,13 +517,13 @@
|
|
|
476
517
|
var(--ui-focus-halo);
|
|
477
518
|
}
|
|
478
519
|
|
|
479
|
-
:host
|
|
520
|
+
:host[data-appearance="solid"]:focus-visible {
|
|
480
521
|
box-shadow:
|
|
481
522
|
var(--ui-button-solid-shadow, var(--ui-action-shadow, var(--_ui-action-shadow))),
|
|
482
523
|
var(--ui-focus-halo);
|
|
483
524
|
}
|
|
484
525
|
|
|
485
|
-
:is(:host
|
|
526
|
+
:is(:host[data-appearance="ghost"], :host[data-appearance="link"], :host[data-appearance="card"]):focus-visible {
|
|
486
527
|
box-shadow: var(--ui-focus-halo);
|
|
487
528
|
}
|
|
488
529
|
|
|
@@ -504,7 +545,7 @@
|
|
|
504
545
|
}
|
|
505
546
|
|
|
506
547
|
/* Ghost keeps no border, but gains the same neutral surface as other disabled buttons. */
|
|
507
|
-
:host
|
|
548
|
+
:host[data-appearance="ghost"]:is(:disabled, [aria-disabled="true"]:not([aria-busy="true"])) {
|
|
508
549
|
background-color: var(--ui-disabled-surface);
|
|
509
550
|
border-color: transparent;
|
|
510
551
|
}
|
|
@@ -521,12 +562,12 @@
|
|
|
521
562
|
a link-style one keeps its text size, so a standalone one ("See all" under a list) needs the
|
|
522
563
|
area. xs sits inside a line of text the same way, so it keeps its size and gains the area too.
|
|
523
564
|
Only these need it: a hit area on every button would reach over its neighbours. */
|
|
524
|
-
html[data-ui-input-modality="touch"] :host
|
|
565
|
+
html[data-ui-input-modality="touch"] :host[data-appearance="link"],
|
|
525
566
|
html[data-ui-input-modality="touch"] :host([size="xs"]) {
|
|
526
567
|
position: relative;
|
|
527
568
|
}
|
|
528
569
|
|
|
529
|
-
html[data-ui-input-modality="touch"] :host
|
|
570
|
+
html[data-ui-input-modality="touch"] :host[data-appearance="link"]::after,
|
|
530
571
|
html[data-ui-input-modality="touch"] :host([size="xs"])::after {
|
|
531
572
|
position: absolute;
|
|
532
573
|
inset: 50% auto auto 50%;
|
|
@@ -538,7 +579,7 @@
|
|
|
538
579
|
|
|
539
580
|
/* Link: an inline text action. No box, padding, or minimum height (inline targets are exempt under
|
|
540
581
|
WCAG 2.5.8); the inherited font; an underline that strengthens on hover; a visible focus ring. */
|
|
541
|
-
:host
|
|
582
|
+
:host[data-appearance="link"] {
|
|
542
583
|
display: inline !important;
|
|
543
584
|
min-block-size: 0;
|
|
544
585
|
padding: 0 !important;
|
|
@@ -555,24 +596,24 @@
|
|
|
555
596
|
transition: color var(--ui-motion-fast) var(--ui-motion-ease), text-decoration-color var(--ui-motion-fast) var(--ui-motion-ease);
|
|
556
597
|
}
|
|
557
598
|
|
|
558
|
-
:host
|
|
599
|
+
:host[data-appearance="link"]:hover:not(:disabled, [aria-disabled="true"]) {
|
|
559
600
|
color: var(--ui-button-link-hover-text, var(--ui-text-primary));
|
|
560
601
|
text-decoration-color: currentColor;
|
|
561
602
|
background: none;
|
|
562
603
|
transform: none;
|
|
563
604
|
}
|
|
564
605
|
|
|
565
|
-
:host
|
|
606
|
+
:host[data-appearance="link"]:active:not(:disabled, [aria-disabled="true"]) {
|
|
566
607
|
background: none;
|
|
567
608
|
transform: none;
|
|
568
609
|
}
|
|
569
610
|
|
|
570
|
-
:host
|
|
611
|
+
:host[data-appearance="link"]:is(:disabled, [aria-disabled="true"]:not([aria-busy="true"])) {
|
|
571
612
|
background: none;
|
|
572
613
|
border-color: transparent;
|
|
573
614
|
}
|
|
574
615
|
|
|
575
|
-
:host
|
|
616
|
+
:host[data-appearance="link"]:focus-visible {
|
|
576
617
|
box-shadow: none;
|
|
577
618
|
/* Fallback keeps the ring if the token is missing: an unresolved var() would void the outline. */
|
|
578
619
|
outline: var(--ui-focus-width) solid var(--ui-focus-ring);
|
|
@@ -4,11 +4,14 @@
|
|
|
4
4
|
>Optional size preset. content keeps the existing intrinsic sizing; the other sizes constrain the surface for compact, medium, or large content.</prop>
|
|
5
5
|
<prop name="variant" type="keyword" values="outlined, subtle, elevated" default="outlined"
|
|
6
6
|
>The surface. outlined is a bordered panel on the page; subtle is a quiet filled one with no border, for supporting details set apart from the flow;
|
|
7
|
-
elevated
|
|
7
|
+
elevated adds a subtle lift above the page, for a view's one card, such as sign-in. Treatment independently controls decorative material.</prop>
|
|
8
|
+
<prop name="treatment" type="keyword" values="standard, expressive" default="standard"
|
|
9
|
+
>How much of the theme's decorative material the surface expresses. standard preserves its restrained treatment; expressive uses the full material treatment.
|
|
10
|
+
This does not add elevation or require glass.</prop>
|
|
8
11
|
<prop name="padding" type="keyword" values="xs, sm, md, lg" default="md"
|
|
9
12
|
>The space inside the card: xs is compact (0.5rem), sm is snug (0.75rem), md is the default (1.5rem), and lg is roomy (2rem).</prop>
|
|
10
13
|
<prop name="tone" type="keyword" values="neutral, accent, info, success, warning, danger" default="neutral"
|
|
11
|
-
>accent, info, success and
|
|
14
|
+
>accent, info, success, warning and danger add a quiet semantic surface without changing card geometry. Row highlights follow the
|
|
12
15
|
surface hue; action controls retain their own intent.</prop>
|
|
13
16
|
<prop name="as" type="keyword" values="div, section, article, aside" default="div"
|
|
14
17
|
>The native element the card renders. Choose the one that names the content: a section of a page, a self-contained article, or an aside.</prop>
|
|
@@ -24,6 +27,7 @@
|
|
|
24
27
|
needs no layout of its own. */
|
|
25
28
|
:host {
|
|
26
29
|
--_ui-card-surface: var(--ui-surface-raised);
|
|
30
|
+
--_ui-card-elevation: 0 0 0 transparent;
|
|
27
31
|
--_ui-selection-context-surface: initial;
|
|
28
32
|
--_ui-selection-context-border: initial;
|
|
29
33
|
--_ui-selection-context-text: initial;
|
|
@@ -58,10 +62,54 @@
|
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
:host([variant="elevated"]) {
|
|
65
|
+
--_ui-card-elevation: var(--ui-shadow-lg);
|
|
61
66
|
border-color: var(--ui-border-subtle);
|
|
62
67
|
box-shadow: var(--ui-shadow-lg);
|
|
63
68
|
}
|
|
64
69
|
|
|
70
|
+
:host([treatment="expressive"]) {
|
|
71
|
+
--_ui-panel-glass: clamp(0, var(--ui-glassiness, 0), 1);
|
|
72
|
+
--_ui-panel-separation: clamp(0, var(--ui-surface-separation, 0), 1);
|
|
73
|
+
--_ui-panel-rim: calc((var(--_ui-panel-separation) + var(--_ui-panel-glass) * 0.25) * 0.65 * 8px);
|
|
74
|
+
border-width: calc(var(--ui-border-width) + var(--_ui-panel-rim));
|
|
75
|
+
border-color: transparent;
|
|
76
|
+
background:
|
|
77
|
+
var(--ui-surface-highlight, linear-gradient(transparent, transparent)) padding-box,
|
|
78
|
+
linear-gradient(var(--_ui-card-surface), var(--_ui-card-surface)) padding-box,
|
|
79
|
+
linear-gradient(var(--ui-material-glass, var(--ui-surface-raised)), var(--ui-material-glass, var(--ui-surface-raised))) border-box,
|
|
80
|
+
var(--_ui-card-surface) padding-box;
|
|
81
|
+
box-shadow:
|
|
82
|
+
var(--_ui-card-elevation),
|
|
83
|
+
var(--ui-material-glow, 0 0 transparent),
|
|
84
|
+
inset 0 0 0 var(--ui-border-width) var(--ui-material-edge, var(--ui-border-strong));
|
|
85
|
+
backdrop-filter: blur(calc(var(--_ui-panel-glass) * 0.65 * 18px)) saturate(calc(1 + var(--_ui-panel-glass) * 0.65 * 0.18));
|
|
86
|
+
-webkit-backdrop-filter: blur(calc(var(--_ui-panel-glass) * 0.65 * 18px)) saturate(calc(1 + var(--_ui-panel-glass) * 0.65 * 0.18));
|
|
87
|
+
}
|
|
88
|
+
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
|
|
89
|
+
:host([treatment="expressive"]) {
|
|
90
|
+
background: var(--_ui-card-surface);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
94
|
+
:host([treatment="expressive"]) {
|
|
95
|
+
background: var(--_ui-card-surface);
|
|
96
|
+
border-width: var(--ui-border-width);
|
|
97
|
+
border-color: var(--ui-border-strong);
|
|
98
|
+
backdrop-filter: none;
|
|
99
|
+
-webkit-backdrop-filter: none;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
@media (forced-colors: active) {
|
|
103
|
+
:host([treatment="expressive"]) {
|
|
104
|
+
background: Canvas;
|
|
105
|
+
color: CanvasText;
|
|
106
|
+
border: var(--ui-border-width) solid CanvasText;
|
|
107
|
+
box-shadow: none;
|
|
108
|
+
backdrop-filter: none;
|
|
109
|
+
-webkit-backdrop-filter: none;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
65
113
|
:host([tone="accent"]) {
|
|
66
114
|
border-color: color-mix(in srgb, var(--ui-accent) 25%, var(--ui-border-default));
|
|
67
115
|
--_ui-card-surface: color-mix(in srgb, var(--ui-accent) 6%, var(--ui-surface-default));
|
|
@@ -88,10 +136,6 @@
|
|
|
88
136
|
|
|
89
137
|
:host([tone="danger"]) {
|
|
90
138
|
border-color: color-mix(in srgb, var(--ui-danger) 45%, var(--ui-border-default));
|
|
91
|
-
border-inline-start: var(--ui-accent-line-width) solid var(--ui-danger);
|
|
92
|
-
/* An accent edge keeps the corners on its side straight. */
|
|
93
|
-
border-start-start-radius: 0;
|
|
94
|
-
border-end-start-radius: 0;
|
|
95
139
|
--_ui-card-surface: var(--ui-danger-soft);
|
|
96
140
|
}
|
|
97
141
|
|
|
@@ -130,7 +130,8 @@
|
|
|
130
130
|
margin: max(0px, calc((1lh - var(--_ui-checkbox-size, 1.125rem)) / 2)) 0 0 !important;
|
|
131
131
|
border: var(--ui-border-width) solid var(--ui-control-border, var(--ui-border-strong));
|
|
132
132
|
border-radius: min(var(--ui-radius-sm), calc(var(--_ui-checkbox-size, 1.125rem) / 4));
|
|
133
|
-
background: var(--ui-control-surface, var(--ui-surface-default));
|
|
133
|
+
background-color: var(--ui-control-surface, var(--ui-surface-default));
|
|
134
|
+
background-image: var(--ui-surface-highlight, none);
|
|
134
135
|
box-shadow: var(--ui-control-inset);
|
|
135
136
|
cursor: pointer;
|
|
136
137
|
transition:
|
|
@@ -149,7 +150,8 @@
|
|
|
149
150
|
input:checked,
|
|
150
151
|
input:indeterminate {
|
|
151
152
|
border-color: var(--ui-accent-solid);
|
|
152
|
-
background: var(--ui-accent-solid);
|
|
153
|
+
background-color: var(--ui-accent-solid);
|
|
154
|
+
background-image: var(--ui-accent-highlight, none);
|
|
153
155
|
box-shadow: var(--ui-control-inset);
|
|
154
156
|
}
|
|
155
157
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function curvePoints(value) {
|
|
2
|
+
const presets = { linear: [0, 0, 1, 1], ease: [.25, .1, .25, 1], "ease-in": [.42, 0, 1, 1], "ease-out": [0, 0, .58, 1], "ease-in-out": [.42, 0, .58, 1] };
|
|
3
|
+
if (presets[value]) return [...presets[value]];
|
|
4
|
+
const match = /^cubic-bezier\(\s*([^)]*)\s*\)$/.exec(String(value));
|
|
5
|
+
const parts = match?.[1].split(",");
|
|
6
|
+
const points = parts?.map(part => part.trim() === "" ? NaN : Number(part));
|
|
7
|
+
if (!points || points.length !== 4 || points.some(number => !Number.isFinite(number))) throw new Error("Use a cubic-bezier curve.");
|
|
8
|
+
return points.map((number, index) => Math.min(index % 2 ? 2 : 1, Math.max(index % 2 ? -1 : 0, number)));
|
|
9
|
+
}
|
|
10
|
+
export function curveText(points) { return `cubic-bezier(${points.map(number => Number(number.toFixed(3))).join(", ")})`; }
|