actual-css 0.5.0 → 0.7.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/CHANGELOG.md +63 -0
- package/dist/actual.css +1 -0
- package/dist/actual.full.css +589 -131
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/dist/actual.min.css +1 -1
- package/docs/design-notes/browser-support.md +39 -14
- package/docs/design-notes/css-anchor.md +1 -1
- package/docs/design-notes/enhancement-contract.md +30 -13
- package/docs/design-notes/forced-colors.md +11 -7
- package/docs/design-notes/grid.md +4 -4
- package/docs/design-notes/naming.md +15 -9
- package/docs/design-notes/platform-alignment.md +60 -0
- package/docs/design-notes/popover-manual-poc.md +248 -0
- package/docs/design-notes/references.md +4 -0
- package/docs/design-notes/steps.md +748 -0
- package/docs/design-notes/surface-reparenting.md +122 -0
- package/docs/design-notes/widget-primitives.md +32 -15
- package/docs/navigation.json +1 -0
- package/docs/pages/components/alert.md +13 -8
- package/docs/pages/components/aura.md +102 -6
- package/docs/pages/components/avatar.md +9 -6
- package/docs/pages/components/badge.md +11 -7
- package/docs/pages/components/button.md +17 -17
- package/docs/pages/components/card.md +15 -8
- package/docs/pages/components/chat.md +1 -1
- package/docs/pages/components/dialog.md +10 -7
- package/docs/pages/components/drawer.md +1 -1
- package/docs/pages/components/fab.md +12 -10
- package/docs/pages/components/indicator.md +6 -6
- package/docs/pages/components/list.md +11 -2
- package/docs/pages/components/navbar.md +1 -1
- package/docs/pages/components/pagination.md +1 -1
- package/docs/pages/components/rating.md +4 -4
- package/docs/pages/components/skeleton.md +1 -1
- package/docs/pages/components/spinner.md +1 -1
- package/docs/pages/components/steps.md +376 -52
- package/docs/pages/components/table.md +5 -5
- package/docs/pages/enhancements/flyout.md +104 -12
- package/docs/pages/enhancements/overview.md +2 -2
- package/docs/pages/enhancements/scrollspy.md +1 -1
- package/docs/pages/enhancements/status-bar.md +20 -0
- package/docs/pages/enhancements/tabs.md +1 -1
- package/docs/pages/enhancements/tooltip.md +12 -0
- package/docs/pages/examples/overview.md +2 -0
- package/docs/pages/forms/choice-cards.md +4 -4
- package/docs/pages/forms/combobox.md +87 -0
- package/docs/pages/forms/floating-field.md +5 -3
- package/docs/pages/forms/joined-controls.md +4 -4
- package/docs/pages/forms/labels.md +8 -5
- package/docs/pages/forms/overview.md +25 -18
- package/docs/pages/forms/password.md +3 -2
- package/docs/pages/forms/range.md +13 -2
- package/docs/pages/forms/states-and-sizes.md +6 -4
- package/docs/pages/forms/validation.md +13 -9
- package/docs/pages/foundations/philosophy.md +15 -1
- package/docs/pages/foundations/theming.md +8 -0
- package/docs/pages/foundations/tokens.md +9 -6
- package/docs/pages/foundations/typography.md +1 -1
- package/docs/pages/guides/bootstrap.md +3 -3
- package/docs/pages/guides/introduction.md +1 -1
- package/docs/pages/guides/modular-import.md +21 -10
- package/docs/pages/guides/progressive-enhancement.md +96 -73
- package/docs/pages/guides/tailwind.md +2 -2
- package/docs/pages/layout/app-shell.md +4 -4
- package/docs/pages/layout/choosing.md +10 -6
- package/docs/pages/layout/grid.md +11 -10
- package/docs/pages/layout/stack.md +10 -6
- package/docs/pages/patterns/structured-lists.md +7 -7
- package/docs/pages/utilities/accessibility.md +1 -1
- package/docs/pages/utilities/flex.md +20 -13
- package/docs/pages/utilities/links.md +1 -1
- package/docs/pages/utilities/list.md +1 -1
- package/docs/pages/utilities/overflow.md +14 -7
- package/docs/pages/utilities/overview.md +22 -0
- package/docs/pages/utilities/shape.md +1 -1
- package/docs/pages/utilities/sizing-wrapping.md +10 -8
- package/docs/pages/utilities/spacing.md +26 -19
- package/docs/pages/utilities/text.md +1 -1
- package/llms.txt +27 -4
- package/package.json +7 -4
- package/scripts/reserved-classes.json +4 -1
- package/src/css/components/fab.css +44 -8
- package/src/css/components/flyout.css +49 -3
- package/src/css/components/join.css +7 -0
- package/src/css/components/list.css +3 -1
- package/src/css/components/menu.css +16 -10
- package/src/css/components/steps.css +277 -69
- package/src/css/components/tooltip.css +37 -1
- package/src/css/core/tokens.css +1 -0
- package/src/css/effects/aura.css +101 -11
- package/src/css/forms/custom-select.css +1 -1
- package/src/css/forms/floating-field.css +8 -6
- package/src/css/layout/container-query.css +37 -13
- package/src/css/layout/grid.css +7 -7
- package/src/css/layout/index.css +1 -1
- package/src/js/password.js +1 -1
- package/src/js/status.js +38 -2
- package/src/js/surface.js +28 -12
- package/src/js/tooltip.js +2 -2
- package/src/js/floating.js +0 -432
package/dist/actual.min.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
*, *::before, *::after { box-sizing: border-box; } html { -webkit-text-size-adjust: 100%; } body { margin: 0; } button, input, textarea, select { font: inherit; } [hidden]:not([hidden="until-found"]) { display: none !important; } :where(img, svg, video) { max-inline-size: 100%; block-size: auto; } @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto !important; } *, *::before, *::after { animation-duration: 0.01ms !important; animation-delay: 0s !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; transition-delay: 0s !important; } } :root { --font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; --line-height: 1.5; --line-height-tight: 1.25; --line-height-relaxed: 1.75; --font-weight: 400; --font-weight-medium: 500; --font-weight-strong: 650; --font-weight-bold: 700; --font-size-xs: 0.8125rem; --font-size-sm: 0.875rem; --font-size-md: 1rem; --font-size-lg: 1.125rem; --gap: 0.75rem; --density-space: var(--space-40); --density-compact-size: 1.5rem; --space-10: 0.25rem; --space-20: 0.5rem; --space-30: 0.75rem; --space-40: 1rem; --space-50: 1.5rem; --space-60: 2rem; --border-width: 1px; --radius-full: 9999px; --shadow: 0 1px 3px rgb(0 0 0 / 0.05), 0 4px 12px rgb(0 0 0 / 0.04); --shadow-popout: 0 0.25rem 0.75rem rgb(0 0 0 / 0.15); --duration: 150ms; --duration-fast: 100ms; --duration-slow: 200ms; --duration-spin: 0.75s; --duration-shimmer: 1.2s; --ease-enter: cubic-bezier(0.2, 0, 0, 1); --ease-exit: cubic-bezier(0.4, 0, 1, 1); --focus-outline-color: currentColor; --focus-outline: calc(var(--border-width) * 2) solid var(--focus-outline-color); --focus-outline-offset: 2px; --focus-ring-width: 3px; --indicator-offset: 14.65%; --backdrop-color: rgb(0 0 0); --backdrop-opacity: 0.45; --backdrop-fill: rgb(0 0 0 / var(--backdrop-opacity)); --bar-height: 0.65rem; --control-size-sm: 2rem; --control-size-md: 2.375rem; --control-size-lg: 2.75rem; --control-size: var(--control-size-md); --control-pad-x: 1em; --control-font-size: var(--font-size-md); --choice-control-size: 1.125em; --disabled-opacity: 0.65; } :root, [data-theme] { --state-selected: var(--primary); --state-selected-fg: var(--primary-fg); --state-disabled: var(--text-subtle); --indicator-ring: var(--surface); } :root { --soft-bg-mix: 88%; --soft-border-mix: 65%; --soft-hover-alpha: 12%; --soft-fg-mix: 100%; --icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%238a8594' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E"); --icon-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E"); --icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='6' y1='6' x2='18' y2='18'/%3E%3Cline x1='18' y1='6' x2='6' y2='18'/%3E%3C/svg%3E"); --viewport-block: 100vh; --viewport-inline: 100vw; --z-sticky: 10; --z-menu: 20; --z-tooltip: 50; --z-status: 60; } @media (forced-colors: active) { :root, [data-theme] { --state-disabled: GrayText; --disabled-opacity: 1; } } @supports (block-size: 100dvh) { :root { --viewport-block: 100dvh; } } @supports (inline-size: 100vi) { :root { --viewport-inline: 100vi; } } @supports (color: color-mix(in oklch, red, white)) { :root, [data-theme] { --shadow: 0 1px 3px color-mix(in oklch, var(--shadow-color) 5%, transparent), 0 4px 12px color-mix(in oklch, var(--shadow-color) 4%, transparent); --shadow-popout: 0 0.25rem 0.75rem color-mix(in oklch, var(--shadow-color) 15%, transparent); } :root { --backdrop-fill: color-mix( in oklch, var(--backdrop-color) calc(var(--backdrop-opacity) * 100%), transparent ); } } :root { color-scheme: light dark; } [data-theme="light"] { color-scheme: light; } [data-theme="dark"] { color-scheme: dark; } @supports not (color: light-dark(red, blue)) { :root, [data-theme="light"], [data-theme="dark"] { color-scheme: light; } } :root, [data-theme="light"] { --primary: hsl(268 32% 33%); --primary-fg: hsl(0 0% 98%); --secondary: hsl(16 38% 38%); --secondary-fg: hsl(0 0% 98%); --success: hsl(150 40% 31%); --success-fg: hsl(0 0% 98%); --warning: hsl(36 68% 34%); --warning-fg: hsl(0 0% 98%); --danger: hsl(5 58% 43%); --danger-fg: hsl(0 0% 98%); --neutral: hsl(258 7% 43%); --neutral-fg: hsl(0 0% 98%); --surface: hsl(0 0% 100%); --surface-raised: hsl(0 0% 100%); --surface-subtle: hsl(262 24% 96%); --surface-solid: hsl(261 16% 13%); --text: hsl(261 20% 13%); --text-muted: hsl(259 9% 42%); --text-subtle: hsl(258 7% 57%); --border: hsl(260 15% 88%); --focus: var(--neutral); --focus-ring: hsl(258 7% 43% / 0.3); --hover-overlay: hsl(260 20% 10% / 0.04); --radius: 0.5rem; --radius-sm: 0.25rem; --radius-lg: 0.75rem; --shadow-color: hsl(260 25% 10%); --hgroup-gap: 0.25em; } :root, [data-theme] { --heading: var(--text); --selection-bg: var(--primary); --selection-fg: var(--primary-fg); --focus-ring-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring); } :root, [data-theme] { accent-color: var(--primary); } [data-tone="dark"] { --tone-fg: white; } [data-tone="light"] { --tone-fg: var(--text); } @supports (color: color-mix(in oklch, red, white)) { :root, [data-theme] { --focus-ring: color-mix(in oklch, var(--focus) 30%, transparent); } } @supports (color: light-dark(red, blue)) { :root, [data-theme="light"] { --primary: light-dark(hsl(268 32% 33%), hsl(268 42% 76%)); --primary-fg: light-dark(hsl(0 0% 98%), hsl(268 30% 12%)); --secondary: light-dark(hsl(16 38% 38%), hsl(18 45% 72%)); --secondary-fg: light-dark(hsl(0 0% 98%), hsl(18 32% 11%)); --success: light-dark(hsl(150 40% 31%), hsl(150 38% 62%)); --success-fg: light-dark(hsl(0 0% 98%), hsl(150 35% 8%)); --warning: light-dark(hsl(36 68% 34%), hsl(40 62% 64%)); --warning-fg: light-dark(hsl(0 0% 98%), hsl(38 35% 10%)); --danger: light-dark(hsl(5 58% 43%), hsl(4 60% 70%)); --danger-fg: light-dark(hsl(0 0% 98%), hsl(4 35% 10%)); --neutral: light-dark(hsl(258 7% 43%), hsl(258 10% 72%)); --neutral-fg: light-dark(hsl(0 0% 98%), hsl(260 18% 10%)); --surface: light-dark(hsl(0 0% 100%), hsl(263 17% 9%)); --surface-raised: light-dark(hsl(0 0% 100%), hsl(263 15% 12%)); --surface-subtle: light-dark(hsl(262 24% 96%), hsl(263 13% 16%)); --surface-solid: light-dark(hsl(261 16% 13%), hsl(0 0% 96%)); --text: light-dark(hsl(261 20% 13%), hsl(260 12% 95%)); --text-muted: light-dark(hsl(259 9% 42%), hsl(259 9% 67%)); --text-subtle: light-dark(hsl(258 7% 57%), hsl(258 7% 54%)); --border: light-dark(hsl(260 15% 88%), hsl(262 12% 24%)); --focus-ring: light-dark(hsl(258 7% 43% / 0.3), hsl(258 10% 72% / 0.28)); --hover-overlay: light-dark(hsl(260 20% 10% / 0.04), hsl(0 0% 100% / 0.06)); --shadow-color: light-dark(hsl(260 25% 10%), hsl(262 40% 2%)); } } @supports (color: color-mix(in oklch, red, white)) and (color: light-dark(red, blue)) { :root, [data-theme] { --focus-ring: light-dark( color-mix(in oklch, var(--focus) 30%, transparent), color-mix(in oklch, var(--focus) 28%, transparent) ); } } @media (prefers-contrast: more) { :root, [data-theme][data-theme] { --border-width: 2px; --focus-outline-offset: 3px; --focus-ring-width: 4px; --text-muted: var(--text); --text-subtle: var(--text); } } body { background: var(--surface); color: var(--text); font-family: var(--font-sans); line-height: var(--line-height); } a { color: var(--link, inherit); text-underline-offset: 0.18em; } a:hover { text-decoration-thickness: 0.12em; } :where(h1, h2, h3, h4, h5, h6) { color: var(--heading, inherit); line-height: var(--line-height-tight); text-wrap: balance; } hgroup { display: grid; gap: var(--hgroup-gap); } hgroup > * { margin-block: 0; } hr { border: 0; border-block-start: var(--border-width) solid var(--border); margin-block: var(--space-60); } code, kbd, samp, pre { font-family: var(--font-mono); } small { font-size: 0.875em; } ::selection { background: var(--selection-bg, var(--primary)); color: var(--selection-fg, var(--primary-fg)); } .primary { --intent: var(--primary); --intent-fg: var(--primary-fg); } .secondary { --intent: var(--secondary); --intent-fg: var(--secondary-fg); } .success { --intent: var(--success); --intent-fg: var(--success-fg); } .warning { --intent: var(--warning); --intent-fg: var(--warning-fg); } .danger { --intent: var(--danger); --intent-fg: var(--danger-fg); } .neutral { --intent: var(--neutral); --intent-fg: var(--neutral-fg); } .intent-color { color: var(--intent); } .solid { --ui-bg: var(--intent, var(--neutral)); --ui-fg: var(--intent-fg, var(--neutral-fg)); --ui-border: transparent; } .soft { --ui-bg: var(--surface-subtle); --ui-border: var(--border); --ui-fg: var(--intent, var(--text)); } .outline { --ui-bg: transparent; --ui-fg: var(--intent, currentColor); --ui-border: var(--intent, currentColor); } .surface { --ui-bg: var(--surface); --ui-fg: var(--intent, var(--text)); --ui-border: var(--border); } .inverted { --ui-bg: var(--surface-solid); --ui-fg: var(--surface); --ui-border: transparent; --ui-hover-bg: var(--surface-solid); --heading: var(--surface); --busy-overlay-bg: var(--surface-solid); background: var(--ui-bg); color: var(--ui-fg); border-color: var(--ui-border); } .sm { --gap: var(--space-20); --density-space: var(--space-20); --control-size: var(--control-size-sm); --density-compact-size: 1.25rem; } .lg { --gap: var(--space-50); --density-space: var(--space-50); --control-size: var(--control-size-lg); --density-compact-size: 1.875rem; } @supports (color: color-mix(in oklch, red, white)) { .soft { --ui-bg: color-mix(in oklab, var(--surface) var(--soft-bg-mix), var(--intent, var(--neutral))); --ui-border: color-mix( in oklab, var(--surface) var(--soft-border-mix), var(--intent, var(--neutral)) ); --ui-fg: color-mix(in oklab, var(--text), var(--intent, var(--text)) var(--soft-fg-mix)); } .inverted { --ui-hover-bg: color-mix(in oklch, var(--surface-solid) 88%, black); } } :focus { outline: var(--focus-outline); outline-offset: var(--focus-outline-offset); } @supports selector(:focus-visible) { :focus:not(:focus-visible) { outline: none; } } @media print { :root, body { color-scheme: light; background: #fff; color: #000; } h1, h2, h3, h4, h5, h6 { break-after: avoid; } }
|
|
1
|
+
*, *::before, *::after { box-sizing: border-box; } html { -webkit-text-size-adjust: 100%; } body { margin: 0; } button, input, textarea, select { font: inherit; } [hidden]:not([hidden="until-found"]) { display: none !important; } :where(img, svg, video) { max-inline-size: 100%; block-size: auto; } @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto !important; } *, *::before, *::after { animation-duration: 0.01ms !important; animation-delay: 0s !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; transition-delay: 0s !important; } } :root { --font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; --line-height: 1.5; --line-height-tight: 1.25; --line-height-relaxed: 1.75; --font-weight: 400; --font-weight-medium: 500; --font-weight-strong: 650; --font-weight-bold: 700; --font-size-xs: 0.8125rem; --font-size-sm: 0.875rem; --font-size-md: 1rem; --font-size-lg: 1.125rem; --gap: 0.75rem; --density-space: var(--space-40); --density-compact-size: 1.5rem; --space-10: 0.25rem; --space-20: 0.5rem; --space-30: 0.75rem; --space-40: 1rem; --space-50: 1.5rem; --space-60: 2rem; --border-width: 1px; --radius-full: 9999px; --shadow: 0 1px 3px rgb(0 0 0 / 0.05), 0 4px 12px rgb(0 0 0 / 0.04); --shadow-popout: 0 0.25rem 0.75rem rgb(0 0 0 / 0.15); --duration: 150ms; --duration-fast: 100ms; --duration-slow: 200ms; --duration-spin: 0.75s; --duration-shimmer: 1.2s; --ease-enter: cubic-bezier(0.2, 0, 0, 1); --ease-exit: cubic-bezier(0.4, 0, 1, 1); --focus-outline-color: currentColor; --focus-outline: calc(var(--border-width) * 2) solid var(--focus-outline-color); --focus-outline-offset: 2px; --focus-ring-width: 3px; --indicator-offset: 14.65%; --backdrop-color: rgb(0 0 0); --backdrop-opacity: 0.45; --backdrop-fill: rgb(0 0 0 / var(--backdrop-opacity)); --bar-height: 0.65rem; --control-size-sm: 2rem; --control-size-md: 2.375rem; --control-size-lg: 2.75rem; --control-size: var(--control-size-md); --control-pad-x: 1em; --control-font-size: var(--font-size-md); --choice-control-size: 1.125em; --disabled-opacity: 0.65; } :root, [data-theme] { --state-selected: var(--primary); --state-selected-fg: var(--primary-fg); --state-disabled: var(--text-subtle); --indicator-ring: var(--surface); } :root { --soft-bg-mix: 88%; --soft-border-mix: 65%; --soft-hover-alpha: 12%; --soft-fg-mix: 100%; --icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%238a8594' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E"); --icon-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E"); --icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='6' y1='6' x2='18' y2='18'/%3E%3Cline x1='18' y1='6' x2='6' y2='18'/%3E%3C/svg%3E"); --icon-plus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='12' y1='5' x2='12' y2='19'/%3E%3Cline x1='5' y1='12' x2='19' y2='12'/%3E%3C/svg%3E"); --viewport-block: 100vh; --viewport-inline: 100vw; --z-sticky: 10; --z-menu: 20; --z-tooltip: 50; --z-status: 60; } @media (forced-colors: active) { :root, [data-theme] { --state-disabled: GrayText; --disabled-opacity: 1; } } @supports (block-size: 100dvh) { :root { --viewport-block: 100dvh; } } @supports (inline-size: 100vi) { :root { --viewport-inline: 100vi; } } @supports (color: color-mix(in oklch, red, white)) { :root, [data-theme] { --shadow: 0 1px 3px color-mix(in oklch, var(--shadow-color) 5%, transparent), 0 4px 12px color-mix(in oklch, var(--shadow-color) 4%, transparent); --shadow-popout: 0 0.25rem 0.75rem color-mix(in oklch, var(--shadow-color) 15%, transparent); } :root { --backdrop-fill: color-mix( in oklch, var(--backdrop-color) calc(var(--backdrop-opacity) * 100%), transparent ); } } :root { color-scheme: light dark; } [data-theme="light"] { color-scheme: light; } [data-theme="dark"] { color-scheme: dark; } @supports not (color: light-dark(red, blue)) { :root, [data-theme="light"], [data-theme="dark"] { color-scheme: light; } } :root, [data-theme="light"] { --primary: hsl(268 32% 33%); --primary-fg: hsl(0 0% 98%); --secondary: hsl(16 38% 38%); --secondary-fg: hsl(0 0% 98%); --success: hsl(150 40% 31%); --success-fg: hsl(0 0% 98%); --warning: hsl(36 68% 34%); --warning-fg: hsl(0 0% 98%); --danger: hsl(5 58% 43%); --danger-fg: hsl(0 0% 98%); --neutral: hsl(258 7% 43%); --neutral-fg: hsl(0 0% 98%); --surface: hsl(0 0% 100%); --surface-raised: hsl(0 0% 100%); --surface-subtle: hsl(262 24% 96%); --surface-solid: hsl(261 16% 13%); --text: hsl(261 20% 13%); --text-muted: hsl(259 9% 42%); --text-subtle: hsl(258 7% 57%); --border: hsl(260 15% 88%); --focus: var(--neutral); --focus-ring: hsl(258 7% 43% / 0.3); --hover-overlay: hsl(260 20% 10% / 0.04); --radius: 0.5rem; --radius-sm: 0.25rem; --radius-lg: 0.75rem; --shadow-color: hsl(260 25% 10%); --hgroup-gap: 0.25em; } :root, [data-theme] { --heading: var(--text); --selection-bg: var(--primary); --selection-fg: var(--primary-fg); --focus-ring-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring); } :root, [data-theme] { accent-color: var(--primary); } [data-tone="dark"] { --tone-fg: white; } [data-tone="light"] { --tone-fg: var(--text); } @supports (color: color-mix(in oklch, red, white)) { :root, [data-theme] { --focus-ring: color-mix(in oklch, var(--focus) 30%, transparent); } } @supports (color: light-dark(red, blue)) { :root, [data-theme="light"] { --primary: light-dark(hsl(268 32% 33%), hsl(268 42% 76%)); --primary-fg: light-dark(hsl(0 0% 98%), hsl(268 30% 12%)); --secondary: light-dark(hsl(16 38% 38%), hsl(18 45% 72%)); --secondary-fg: light-dark(hsl(0 0% 98%), hsl(18 32% 11%)); --success: light-dark(hsl(150 40% 31%), hsl(150 38% 62%)); --success-fg: light-dark(hsl(0 0% 98%), hsl(150 35% 8%)); --warning: light-dark(hsl(36 68% 34%), hsl(40 62% 64%)); --warning-fg: light-dark(hsl(0 0% 98%), hsl(38 35% 10%)); --danger: light-dark(hsl(5 58% 43%), hsl(4 60% 70%)); --danger-fg: light-dark(hsl(0 0% 98%), hsl(4 35% 10%)); --neutral: light-dark(hsl(258 7% 43%), hsl(258 10% 72%)); --neutral-fg: light-dark(hsl(0 0% 98%), hsl(260 18% 10%)); --surface: light-dark(hsl(0 0% 100%), hsl(263 17% 9%)); --surface-raised: light-dark(hsl(0 0% 100%), hsl(263 15% 12%)); --surface-subtle: light-dark(hsl(262 24% 96%), hsl(263 13% 16%)); --surface-solid: light-dark(hsl(261 16% 13%), hsl(0 0% 96%)); --text: light-dark(hsl(261 20% 13%), hsl(260 12% 95%)); --text-muted: light-dark(hsl(259 9% 42%), hsl(259 9% 67%)); --text-subtle: light-dark(hsl(258 7% 57%), hsl(258 7% 54%)); --border: light-dark(hsl(260 15% 88%), hsl(262 12% 24%)); --focus-ring: light-dark(hsl(258 7% 43% / 0.3), hsl(258 10% 72% / 0.28)); --hover-overlay: light-dark(hsl(260 20% 10% / 0.04), hsl(0 0% 100% / 0.06)); --shadow-color: light-dark(hsl(260 25% 10%), hsl(262 40% 2%)); } } @supports (color: color-mix(in oklch, red, white)) and (color: light-dark(red, blue)) { :root, [data-theme] { --focus-ring: light-dark( color-mix(in oklch, var(--focus) 30%, transparent), color-mix(in oklch, var(--focus) 28%, transparent) ); } } @media (prefers-contrast: more) { :root, [data-theme][data-theme] { --border-width: 2px; --focus-outline-offset: 3px; --focus-ring-width: 4px; --text-muted: var(--text); --text-subtle: var(--text); } } body { background: var(--surface); color: var(--text); font-family: var(--font-sans); line-height: var(--line-height); } a { color: var(--link, inherit); text-underline-offset: 0.18em; } a:hover { text-decoration-thickness: 0.12em; } :where(h1, h2, h3, h4, h5, h6) { color: var(--heading, inherit); line-height: var(--line-height-tight); text-wrap: balance; } hgroup { display: grid; gap: var(--hgroup-gap); } hgroup > * { margin-block: 0; } hr { border: 0; border-block-start: var(--border-width) solid var(--border); margin-block: var(--space-60); } code, kbd, samp, pre { font-family: var(--font-mono); } small { font-size: 0.875em; } ::selection { background: var(--selection-bg, var(--primary)); color: var(--selection-fg, var(--primary-fg)); } .primary { --intent: var(--primary); --intent-fg: var(--primary-fg); } .secondary { --intent: var(--secondary); --intent-fg: var(--secondary-fg); } .success { --intent: var(--success); --intent-fg: var(--success-fg); } .warning { --intent: var(--warning); --intent-fg: var(--warning-fg); } .danger { --intent: var(--danger); --intent-fg: var(--danger-fg); } .neutral { --intent: var(--neutral); --intent-fg: var(--neutral-fg); } .intent-color { color: var(--intent); } .solid { --ui-bg: var(--intent, var(--neutral)); --ui-fg: var(--intent-fg, var(--neutral-fg)); --ui-border: transparent; } .soft { --ui-bg: var(--surface-subtle); --ui-border: var(--border); --ui-fg: var(--intent, var(--text)); } .outline { --ui-bg: transparent; --ui-fg: var(--intent, currentColor); --ui-border: var(--intent, currentColor); } .surface { --ui-bg: var(--surface); --ui-fg: var(--intent, var(--text)); --ui-border: var(--border); } .inverted { --ui-bg: var(--surface-solid); --ui-fg: var(--surface); --ui-border: transparent; --ui-hover-bg: var(--surface-solid); --heading: var(--surface); --busy-overlay-bg: var(--surface-solid); background: var(--ui-bg); color: var(--ui-fg); border-color: var(--ui-border); } .sm { --gap: var(--space-20); --density-space: var(--space-20); --control-size: var(--control-size-sm); --density-compact-size: 1.25rem; } .lg { --gap: var(--space-50); --density-space: var(--space-50); --control-size: var(--control-size-lg); --density-compact-size: 1.875rem; } @supports (color: color-mix(in oklch, red, white)) { .soft { --ui-bg: color-mix(in oklab, var(--surface) var(--soft-bg-mix), var(--intent, var(--neutral))); --ui-border: color-mix( in oklab, var(--surface) var(--soft-border-mix), var(--intent, var(--neutral)) ); --ui-fg: color-mix(in oklab, var(--text), var(--intent, var(--text)) var(--soft-fg-mix)); } .inverted { --ui-hover-bg: color-mix(in oklch, var(--surface-solid) 88%, black); } } :focus { outline: var(--focus-outline); outline-offset: var(--focus-outline-offset); } @supports selector(:focus-visible) { :focus:not(:focus-visible) { outline: none; } } @media print { :root, body { color-scheme: light; background: #fff; color: #000; } h1, h2, h3, h4, h5, h6 { break-after: avoid; } }
|
|
@@ -23,6 +23,30 @@ Degraded browsers may still run some JavaScript successfully, but that
|
|
|
23
23
|
behavior is not tested, documented as supported, or preserved when the
|
|
24
24
|
runtime evolves.
|
|
25
25
|
|
|
26
|
+
### Native Popover and the tiers
|
|
27
|
+
|
|
28
|
+
Popover is not part of any Actual tier's guarantee below Recommended. It is a
|
|
29
|
+
capability, not a browser generation: the core lifecycle landed around
|
|
30
|
+
Chromium 116, Firefox 125 and Safari 17, which clears Recommended but sits
|
|
31
|
+
above both Minimal and Intermediate on every engine.
|
|
32
|
+
|
|
33
|
+
| Actual tier | Core native Popover guaranteed | Recommended lifecycle owner |
|
|
34
|
+
| ------------ | ------------------------------ | ------------------------------- |
|
|
35
|
+
| Degraded | no | HTML/CSS fallback only |
|
|
36
|
+
| Minimal | no | `surface.js` |
|
|
37
|
+
| Intermediate | no | `surface.js` |
|
|
38
|
+
| Recommended | yes, core lifecycle | native Popover, or `surface.js` |
|
|
39
|
+
|
|
40
|
+
"Core lifecycle" means `popover="auto"`/`"manual"`, `popovertarget`,
|
|
41
|
+
`showPopover`/`hidePopover` and `:popover-open` — not the entire Popover API.
|
|
42
|
+
`popover="hint"` and interest invokers are much newer and are not covered at
|
|
43
|
+
any tier. Safari coverage here is read as the desktop engine; some iOS versions
|
|
44
|
+
through 18.2 still report partial support for parts of the API, so an
|
|
45
|
+
application targeting iOS should verify the specific pieces it relies on.
|
|
46
|
+
|
|
47
|
+
Actual's presentation classes compose with either lifecycle from Recommended
|
|
48
|
+
up. The runtime itself depends on Popover at no tier.
|
|
49
|
+
|
|
26
50
|
## JavaScript philosophy
|
|
27
51
|
|
|
28
52
|
> The Degraded tier exists primarily to preserve useful HTML and CSS on older
|
|
@@ -83,20 +107,21 @@ The tiers above are Actual's own contract. The table below tracks the Web
|
|
|
83
107
|
Platform Baseline of the individual CSS features the framework uses; it is a
|
|
84
108
|
different notion from an Actual tier.
|
|
85
109
|
|
|
86
|
-
| Feature area
|
|
87
|
-
|
|
88
|
-
| Layout (flex, grid, position)
|
|
89
|
-
| CSS custom properties
|
|
90
|
-
| `:has()`
|
|
91
|
-
| `<dialog>` element
|
|
92
|
-
| `color-mix()`
|
|
93
|
-
| `light-dark()`
|
|
94
|
-
| `:user-invalid`
|
|
95
|
-
| `transition-behavior: allow-discrete` | 2024+
|
|
96
|
-
| `appearance: base-select`
|
|
97
|
-
| `@starting-style`
|
|
98
|
-
| View Transition API
|
|
99
|
-
|
|
|
110
|
+
| Feature area | Baseline | Progressive enhancement |
|
|
111
|
+
| ------------------------------------- | ------------- | ------------------------------------ |
|
|
112
|
+
| Layout (flex, grid, position) | Baseline 2023 | — |
|
|
113
|
+
| CSS custom properties | Baseline 2023 | — |
|
|
114
|
+
| `:has()` | Baseline 2023 | — |
|
|
115
|
+
| `<dialog>` element | Baseline 2023 | — |
|
|
116
|
+
| `color-mix()` | Baseline 2023 | Fallback flat color |
|
|
117
|
+
| `light-dark()` | 2024+ | Manual theme override |
|
|
118
|
+
| `:user-invalid` | 2024+ | `[aria-invalid]` attribute |
|
|
119
|
+
| `transition-behavior: allow-discrete` | 2024+ | Native dialog open/close |
|
|
120
|
+
| `appearance: base-select` | 2024+ | Native `<select>` |
|
|
121
|
+
| `@starting-style` | 2024+ | Instant appearance |
|
|
122
|
+
| View Transition API | 2024+ | Dialog open/close transition |
|
|
123
|
+
| Popover API (core) | Baseline 2025 | `surface.js` lifecycle |
|
|
124
|
+
| CSS Anchor Positioning | Future | JS positioning (`@lekoala/floating`) |
|
|
100
125
|
|
|
101
126
|
Core layout and visual tokens work on all Baseline 2023 browsers. Features marked
|
|
102
127
|
2024+ are gated with `@supports` or degrade gracefully through fallback values.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Context
|
|
4
4
|
|
|
5
|
-
Currently, the positioning logic for tooltips, flyouts, and context menus is handled by
|
|
5
|
+
Currently, the positioning logic for tooltips, flyouts, and context menus is handled by the `@lekoala/floating` JavaScript dependency. This engine computes fixed coordinates, handles viewport boundaries, and applies "flip" and "shift" heuristics to prevent overflow.
|
|
6
6
|
|
|
7
7
|
As of mid-2024, modern browsers started supporting the **CSS Anchor Positioning API**, which delegates this heavy mathematical lifting directly to the browser's layout engine.
|
|
8
8
|
|
|
@@ -105,22 +105,39 @@ defeating the split.
|
|
|
105
105
|
|
|
106
106
|
## Design decisions recorded
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
108
|
+
**D2 — `selectors.js` drops its 5 discovery entries and keeps 11 write-side
|
|
109
|
+
state entries.** The file's job changes from *CSS↔init bridge* to *state
|
|
110
|
+
vocabulary the runtime writes*. It stays the single file to alias for another
|
|
111
|
+
CSS framework.
|
|
112
|
+
|
|
113
|
+
**D4 — `status.js` uses `[data-status][role="status"]`, with no class and no
|
|
114
|
+
token.** The markup already carries `role="status"` everywhere, so semantics
|
|
115
|
+
alone scope the document-wide lookup.
|
|
116
|
+
|
|
117
|
+
**D5 — `<dialog>` gets no token.** It is its own opt-in: semantic, native,
|
|
118
|
+
CSS-agnostic.
|
|
119
|
+
|
|
120
|
+
**D6 — `validation.js` accepts `[data-field-error]` as an alias for
|
|
121
|
+
`.field-error`, and keeps `.field` / `.danger` as an optional, degrading
|
|
122
|
+
presentation adapter.** Validation is the one module with intentional, optional
|
|
123
|
+
CSS coupling. The adapter degrades to a no-op under foreign CSS, while
|
|
124
|
+
`aria-invalid`, focus management, custom rules and server errors all keep
|
|
125
|
+
working.
|
|
126
|
+
|
|
127
|
+
**D7 — Context-menu reuses `surface`, not `flyout`.** The panel gets no token;
|
|
128
|
+
`menuFor()` validates semantically (`menu, [role="menu"]`). A context menu may
|
|
129
|
+
legitimately want the flyout *look* without the flyout *behavior*.
|
|
130
|
+
|
|
131
|
+
**D9 — Orphaned mounted surfaces are closed from the origin's disconnect
|
|
132
|
+
handler.** A `fix:`, not part of the contract itself. Landed independently.
|
|
116
133
|
|
|
117
134
|
## Token vocabulary
|
|
118
135
|
|
|
119
|
-
| Token
|
|
120
|
-
|
|
121
|
-
| `tabs`
|
|
122
|
-
| `flyout`
|
|
123
|
-
| `scrollspy`
|
|
136
|
+
| Token | Behavior | Existing class |
|
|
137
|
+
| ------------ | ------------------------------------------------ | ------------------- |
|
|
138
|
+
| `tabs` | Tab panel switching, arrow-key navigation | `.tabs` |
|
|
139
|
+
| `flyout` | Trigger → panel positioning, open/close | `.flyout` |
|
|
140
|
+
| `scrollspy` | Scroll-driven nav highlighting | `.scrollspy` |
|
|
124
141
|
| `validation` | Form validation, focus management, server errors | `.needs-validation` |
|
|
125
142
|
|
|
126
143
|
Four tokens. Everything else stays self-describing.
|
|
@@ -53,13 +53,17 @@ an essential shape, state, or affordance.
|
|
|
53
53
|
|
|
54
54
|
## Color system keywords by role
|
|
55
55
|
|
|
56
|
-
| Context
|
|
57
|
-
|
|
58
|
-
| Disclosure
|
|
59
|
-
| Button, switch, checkbox |
|
|
60
|
-
| Selected / current state | `Highlight
|
|
61
|
-
| Disabled state
|
|
62
|
-
| Hover indicator
|
|
56
|
+
| Context | Color | Why |
|
|
57
|
+
| ------------------------ | --------------------- | ---------------------------------- |
|
|
58
|
+
| Disclosure marker | `CanvasText` | Summary is on Canvas, not a button |
|
|
59
|
+
| Button, switch, checkbox | the `Button*` pair | Interactive controls |
|
|
60
|
+
| Selected / current state | the `Highlight*` pair | Matches OS selection |
|
|
61
|
+
| Disabled state | `GrayText` | Standard disabled system color |
|
|
62
|
+
| Hover indicator | outline or underline | Avoids blocked backgrounds |
|
|
63
|
+
|
|
64
|
+
Controls use `ButtonText` / `ButtonFace`, and `Highlight` / `HighlightText` for
|
|
65
|
+
their selected state. A hover indicator is a `ButtonText` outline or a
|
|
66
|
+
`CanvasText` underline.
|
|
63
67
|
|
|
64
68
|
Prefer `CanvasText` for non-interactive mark-up content. `CanvasText` and
|
|
65
69
|
`ButtonText` are **not guaranteed identical** — custom HCM palettes can set
|
|
@@ -254,7 +254,7 @@ asks.
|
|
|
254
254
|
It is nevertheless the right **baseline**. Bounded at N, using `auto-fill`, it
|
|
255
255
|
is responsive, overflow-safe, and equal-width at every state, and it needs
|
|
256
256
|
nothing above Actual's Minimal floor. Adopting it as the unenhanced behavior of
|
|
257
|
-
`.grid-N` is what makes the actual-
|
|
257
|
+
`.grid-N` is what makes the actual-container container a precision enhancement rather than
|
|
258
258
|
a prerequisite for responsiveness. This is the single most important difference
|
|
259
259
|
from 0.3.1, where a preset used without a wrapper stayed frozen at N columns
|
|
260
260
|
and was a genuine trap.
|
|
@@ -391,7 +391,7 @@ The extra ancestor is therefore not accidental ceremony.
|
|
|
391
391
|
|
|
392
392
|
It represents a real boundary imposed by the CSS containment model.
|
|
393
393
|
|
|
394
|
-
Actual should not hide that boundary if hiding it requires changing the sizing semantics of the grid. Balanced subdivision keys off a container **named** `actual-
|
|
394
|
+
Actual should not hide that boundary if hiding it requires changing the sizing semantics of the grid. Balanced subdivision keys off a container **named** `actual-container` rather than any anonymous query container, so an incidental `container-type: inline-size` region never changes the grid by accident; `.container-query` is the convenience helper that establishes that named context. The name is generic rather than grid-specific: it means "here is the width you were allocated", and other size-aware components (`.steps` and its label collapse) read the same context. The grid does not own it — it is one consumer.
|
|
395
395
|
|
|
396
396
|
## Out of scope: explicit per-region placement
|
|
397
397
|
|
|
@@ -520,7 +520,7 @@ viewport
|
|
|
520
520
|
|
|
|
521
521
|
named query container
|
|
522
522
|
|
|
|
523
|
-
actual-
|
|
523
|
+
actual-container
|
|
524
524
|
|
|
|
525
525
|
2 / 3 / 4 / 6 stages
|
|
526
526
|
```
|
|
@@ -620,7 +620,7 @@ inside a query container.
|
|
|
620
620
|
.grid space-driven, auto-fit, --grid-min
|
|
621
621
|
.grid-N count-driven baseline, auto-fill bounded at N,
|
|
622
622
|
may pass through non-divisor column counts
|
|
623
|
-
actual-
|
|
623
|
+
actual-container .grid-N balanced subdivision, N -> divisors of N -> 1
|
|
624
624
|
```
|
|
625
625
|
|
|
626
626
|
The whole enhancement lives inside `@supports (container-type: inline-size)`.
|
|
@@ -40,15 +40,21 @@ Actual CSS is intended for new projects. Existing projects with class collisions
|
|
|
40
40
|
|
|
41
41
|
## Utilities
|
|
42
42
|
|
|
43
|
-
Utilities ship in two
|
|
43
|
+
Utilities ship in two modules that follow the same class grammar. Both are
|
|
44
|
+
imported by `utilities/index.css`, so the split is about naming and curation,
|
|
45
|
+
never about opting in.
|
|
44
46
|
|
|
45
|
-
- **Base utilities
|
|
46
|
-
(`.overflow-auto`, `.gap-none`, `.text-nowrap`, `.px`, `.py`).
|
|
47
|
-
- **
|
|
47
|
+
- **Base** (`utilities/base.css`) is a curated set of common ergonomic
|
|
48
|
+
shortcuts (`.overflow-auto`, `.gap-none`, `.text-nowrap`, `.px`, `.py`).
|
|
49
|
+
- **Extra** (`utilities/extra.css`) uses explicit property/value names
|
|
48
50
|
(`.overflow-hidden`, `.gap-sm`, `.white-space-normal`).
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
Call them base and extra, after the files. "Optional" is the wrong word for
|
|
53
|
+
either: it reads as a layer an author has to opt into, which is true of every
|
|
54
|
+
module outside `core/` and therefore says nothing about these two.
|
|
55
|
+
|
|
56
|
+
Base may use compact names for a small set of frequent operations (`.px`,
|
|
57
|
+
`.py`, `.mbs`, `.mbe`). Extra uses explicit property/value names and must not
|
|
58
|
+
duplicate an operation already provided by base. The two sets may cover the
|
|
59
|
+
same CSS property, but must not expose competing names for the same
|
|
60
|
+
declaration.
|
|
@@ -138,6 +138,57 @@ Actual class + native HTML
|
|
|
138
138
|
|
|
139
139
|
rather than replacing one framework API with another.
|
|
140
140
|
|
|
141
|
+
## Surface responsibility split
|
|
142
|
+
|
|
143
|
+
Anchored surfaces are the clearest case where the platform is absorbing one
|
|
144
|
+
responsibility at a time rather than a whole widget. Keeping the axes named
|
|
145
|
+
separately is what lets each be replaced on its own schedule:
|
|
146
|
+
|
|
147
|
+
```text
|
|
148
|
+
Popover = where the surface lives
|
|
149
|
+
floating = where it is placed
|
|
150
|
+
surface.js = when and how it opens
|
|
151
|
+
flyout / tooltip = what it looks like
|
|
152
|
+
menu = how its content behaves
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The value of the split is that each line can change owner independently.
|
|
156
|
+
Popover can take over the top layer without touching placement. Anchor
|
|
157
|
+
positioning could later take over placement without touching lifecycle — though
|
|
158
|
+
not yet, because the current positioner also reports *placement validity*, which
|
|
159
|
+
the lifecycle uses to close a surface whose anchor has left the viewport, and
|
|
160
|
+
declarative CSS positioning offers no equivalent signal.
|
|
161
|
+
|
|
162
|
+
### Ceding the properties a capability is expressed through
|
|
163
|
+
|
|
164
|
+
Keeping presentation and behavior separate is not enough on its own, because
|
|
165
|
+
CSS expresses some behavior *as* presentation.
|
|
166
|
+
|
|
167
|
+
> A presentation class that composes with `popover` may own its visible display
|
|
168
|
+
> type, but must not override the platform's hidden-state display.
|
|
169
|
+
|
|
170
|
+
`.flyout { display: grid }` is entirely legitimate. The problem is that the UA
|
|
171
|
+
closes a popover with `display: none`, and an author declaration beats the UA
|
|
172
|
+
origin whatever the specificity — so that legitimate choice silently leaves a
|
|
173
|
+
closed popover painted on screen. The class was not implementing the lifecycle;
|
|
174
|
+
it was overriding the property the lifecycle is written in.
|
|
175
|
+
|
|
176
|
+
The general rule, then:
|
|
177
|
+
|
|
178
|
+
> A class that is advertised as composing with a platform capability must cede
|
|
179
|
+
> the properties that capability uses to express itself, for the states the
|
|
180
|
+
> platform owns.
|
|
181
|
+
|
|
182
|
+
This is checkable rather than aspirational: any Actual class that carries a
|
|
183
|
+
`[popover]` reset must also restore `display` for `:not(:popover-open)`.
|
|
184
|
+
|
|
185
|
+
The corresponding trap is adopting a capability in a way that adds an axis
|
|
186
|
+
instead of replacing one. A second surface transport living alongside the
|
|
187
|
+
existing one would leave the DOM-reparenting code in place while doubling the
|
|
188
|
+
behavior matrix, which is the opposite of the deletion this note asks for. The
|
|
189
|
+
acceptance criterion for moving Actual’s own surfaces onto the top layer is
|
|
190
|
+
therefore concrete: the change should remove more surface code than it adds.
|
|
191
|
+
|
|
141
192
|
## Prefer capabilities over browser generations
|
|
142
193
|
|
|
143
194
|
New platform features should not automatically raise Actual's Minimal browser floor.
|
|
@@ -287,6 +338,15 @@ Avoid building a large custom range abstraction around browser-specific pseudo-e
|
|
|
287
338
|
|
|
288
339
|
Prefer native controls and progressive styling.
|
|
289
340
|
|
|
341
|
+
The concrete signal to watch is `appearance: base-slider` with `::slider-track`, `::slider-fill`, and `::slider-thumb`. Checked on Chrome 152: `CSS.supports('appearance', 'base-slider')` and `CSS.supports('selector(::slider-fill)')` are both false, so there is nothing to enhance against yet.
|
|
342
|
+
|
|
343
|
+
Until then, `.range` intentionally omits filled tracks and tick marks. Both are reachable today only through techniques Actual should not take on:
|
|
344
|
+
|
|
345
|
+
- The Chromium filled-track trick needs `overflow: hidden` on the runnable track plus a large `box-shadow` on the thumb. It collides twice with `range.css`: the thumb is wider than the track and would be clipped, and the focus ring already owns the thumb's `box-shadow`.
|
|
346
|
+
- CSS-only tick marks need a step count duplicated out of `min`/`max`/`step` into a custom property, so markup carries the same contract twice and the two can diverge. `datalist` cannot carry it either: its native ticks are part of the appearance that `appearance: none` removes, and no engine renders `<option label>` for a range.
|
|
347
|
+
|
|
348
|
+
When the base-slider parts become interoperable, this becomes a `@supports` enhancement in the shape of `custom-select.css` — less engine-specific CSS, not more.
|
|
349
|
+
|
|
290
350
|
### Navigation
|
|
291
351
|
|
|
292
352
|
Native navigation proposals may eventually provide richer semantics and interaction behavior for complex navigation structures.
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# POC: native popover as the surface transport
|
|
2
|
+
|
|
3
|
+
A record of an experiment, not a plan of record. It exists so the reasoning
|
|
4
|
+
outlives the branch that produced it — including the parts that came out
|
|
5
|
+
negative, which are the ones a future attempt would otherwise repeat.
|
|
6
|
+
|
|
7
|
+
Question asked: can `popover="manual"` replace the DOM reparenting in
|
|
8
|
+
`surface.js` without changing anything else?
|
|
9
|
+
|
|
10
|
+
Answer: yes, and the branch demonstrates it. What blocks shipping is not the
|
|
11
|
+
architecture; it is the browser contract. See
|
|
12
|
+
[browser-support](browser-support.md).
|
|
13
|
+
|
|
14
|
+
## Branch
|
|
15
|
+
|
|
16
|
+
`poc/popover-manual-surfaces`, four commits on top of `0f4f8b7`:
|
|
17
|
+
|
|
18
|
+
| SHA | Step |
|
|
19
|
+
| --------- | ------------------------------------------------- |
|
|
20
|
+
| `bf1e53d` | Pass 1 — manual transport, reparenting removed |
|
|
21
|
+
| `4d598dd` | Extract the popover test stub, cut to the minimum |
|
|
22
|
+
| `ececcd6` | Pass 2 — keep a runtime scrim, fix its placement |
|
|
23
|
+
| `27b34f1` | Finish as a transparent migration |
|
|
24
|
+
|
|
25
|
+
## What the transport actually needs
|
|
26
|
+
|
|
27
|
+
Derived from the branch, not from a compatibility badge. The distinction between
|
|
28
|
+
the two contracts matters: they fail differently, and only the first gates the
|
|
29
|
+
runtime.
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
JS requirement (gates the runtime)
|
|
33
|
+
----------------------------------
|
|
34
|
+
popover="manual"
|
|
35
|
+
showPopover()
|
|
36
|
+
hidePopover()
|
|
37
|
+
top-layer promotion + closed-state display:none
|
|
38
|
+
|
|
39
|
+
CSS enhancement (already @supports-gated, degrades)
|
|
40
|
+
---------------------------------------------------
|
|
41
|
+
:popover-open
|
|
42
|
+
overlay
|
|
43
|
+
transition-behavior: allow-discrete
|
|
44
|
+
@starting-style
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The runtime never reads `:popover-open`. It was removed from `isSurfaceOpen()`
|
|
48
|
+
and from the `autoUpdate` guard because a partially-implemented selector engine
|
|
49
|
+
answers `false` rather than throwing, which is a silent wrong answer (see the
|
|
50
|
+
happy-dom finding below). `.is-open` is the single state the runtime writes and
|
|
51
|
+
reads, and the class the CSS keys on.
|
|
52
|
+
|
|
53
|
+
Two capabilities that are *not* required, which matters because both are newer
|
|
54
|
+
than Popover and would have pushed the floor much higher:
|
|
55
|
+
|
|
56
|
+
- `overlay` / `allow-discrete` — the exit fade. Gated; without it the exit is
|
|
57
|
+
instant and correct.
|
|
58
|
+
- `::backdrop` — proved unusable, see below.
|
|
59
|
+
|
|
60
|
+
## Findings
|
|
61
|
+
|
|
62
|
+
**happy-dom implements no Popover element API.** Version 20.11 has the invoker
|
|
63
|
+
attributes (`popoverTargetElement`, `popoverTargetAction` on button and input)
|
|
64
|
+
but not `showPopover` / `hidePopover` / `togglePopover`, and
|
|
65
|
+
`matches(":popover-open")` answers `false` rather than throwing. The silent
|
|
66
|
+
false is the dangerous half: reading the pseudo-class in `isSurfaceOpen()` made
|
|
67
|
+
all 647 unit tests see "never open". Resolved by reading `.is-open` only, plus
|
|
68
|
+
two no-op stubs in `tests/helpers/popover-stub.js`. Transport behavior moved to
|
|
69
|
+
the browser layer, which is where it belonged.
|
|
70
|
+
|
|
71
|
+
**`::backdrop` cannot replace the sheet scrim.** Negative result, measured, and
|
|
72
|
+
the one most likely to be re-attempted. The native backdrop is painted for a
|
|
73
|
+
manual popover, covers the viewport, and does inherit custom properties from
|
|
74
|
+
its originating element — a scoped `--backdrop-fill` on an ancestor of the
|
|
75
|
+
panel resolved into it, which a body-level div cannot do. But the UA sets
|
|
76
|
+
`pointer-events: none` on an open popover's backdrop and it is not overridable
|
|
77
|
+
from author CSS: both `pointer-events: auto` and `pointer-events: auto
|
|
78
|
+
!important` computed as `none`, since a UA `!important` outranks an author one.
|
|
79
|
+
Measured with a real CDP click on the scrim, the element behind it received the
|
|
80
|
+
click and its handler fired. The scrim has two jobs — painting, and absorbing
|
|
81
|
+
the pointer — and the platform only offers the first, so it stays an Actual
|
|
82
|
+
element.
|
|
83
|
+
|
|
84
|
+
**The scrim's placement, not the scrim, was what pass 1 broke.**
|
|
85
|
+
`menu.before(backdrop)` put it beside a panel that no longer moves, stranding
|
|
86
|
+
it inside the author's container. It goes to the document root instead, which
|
|
87
|
+
reproduces exactly where it landed before. This is the one fragment of
|
|
88
|
+
`getSurfaceRoot()` that survives, and it is not the reparenting the transport
|
|
89
|
+
removed: the scrim is runtime-created, has no author identity, and nothing
|
|
90
|
+
inherited to preserve.
|
|
91
|
+
|
|
92
|
+
**A surface inside a modal dialog works.** This was the open question, since a
|
|
93
|
+
modal dialog inerts the rest of the document — which is why the old transport
|
|
94
|
+
moved the panel into the dialog. Measured: the panel is promoted above the
|
|
95
|
+
dialog, stays in the dialog subtree and in its own host element, wins the hit
|
|
96
|
+
test, and takes focus.
|
|
97
|
+
|
|
98
|
+
**`aria-expanded` is computed natively for a `popovertarget` invoker** —
|
|
99
|
+
`expanded: false` closed, `true` open, on the AX tree. Deliberately not
|
|
100
|
+
exploited: the public contract is `aria-controls`, so `popovertarget` would be
|
|
101
|
+
a markup migration for adopters rather than an internal transport detail, and
|
|
102
|
+
`linkedTriggers()` supports several triggers per panel where `popovertarget`
|
|
103
|
+
covers one.
|
|
104
|
+
|
|
105
|
+
## Measurements
|
|
106
|
+
|
|
107
|
+
| Measure | `master` | POC |
|
|
108
|
+
| ------------------------- | -------: | --------------: |
|
|
109
|
+
| `surface.js` code lines | 358 | **337** |
|
|
110
|
+
| `surface.js` total lines | 431 | 436 |
|
|
111
|
+
| Transport concepts | 4 | **1** |
|
|
112
|
+
| Inherited contexts broken | 3/3 | **0/3** |
|
|
113
|
+
| Unit tests | 647 | **650**, 0 fail |
|
|
114
|
+
| Browser tests | 50 | **55**, 0 fail |
|
|
115
|
+
| JS full, brotli | 16.1 KB | **16.0 KB** |
|
|
116
|
+
| Public contracts changed | — | **none** |
|
|
117
|
+
|
|
118
|
+
Removed: `mountedSurfaces`, `getSurfaceRoot()`, `mountSurface()`,
|
|
119
|
+
`restoreSurface()`, `disconnectSurface`'s `{ restore }` option, and the
|
|
120
|
+
`[popover]` conflict warning — the runtime is that owner now.
|
|
121
|
+
|
|
122
|
+
The total line count *grows* by five. The 21 fewer code lines are outweighed by
|
|
123
|
+
added comments. "Remove more than you add" is a signal, not a rule: what leaves
|
|
124
|
+
is a concept — a component no longer has to leave its place in the DOM to be
|
|
125
|
+
painted above the page — plus a defect with no generic repair and three
|
|
126
|
+
transport abstractions.
|
|
127
|
+
|
|
128
|
+
## Acceptance criterion
|
|
129
|
+
|
|
130
|
+
> A transport migration may change assertions about transport markers, but must
|
|
131
|
+
> not require changing behavioral assertions.
|
|
132
|
+
|
|
133
|
+
This is stricter and more useful than "no public API change". The 57
|
|
134
|
+
`hidden → .is-open` edits qualify: they replace observing a mechanism with
|
|
135
|
+
observing the lifecycle that owns it. If a future attempt has to modify an
|
|
136
|
+
assertion about focus, dismissal, positioning or events to make the transport
|
|
137
|
+
pass, it is no longer a transport swap and the claim does not hold.
|
|
138
|
+
|
|
139
|
+
## Go / no-go: WebKit verification
|
|
140
|
+
|
|
141
|
+
Every measurement in this note was taken on Chromium — the browser test harness
|
|
142
|
+
hardcodes `backend: "chrome"`. The floor's most notable step is Safari 17, so
|
|
143
|
+
the transport has no measured evidence on the engine the raise actually buys.
|
|
144
|
+
This is the one open technical gate on the migration, and the only item on the
|
|
145
|
+
checklist below that is not already demonstrated.
|
|
146
|
+
|
|
147
|
+
A permanent WebKit matrix is disproportionate for a decision taken once. One
|
|
148
|
+
manual pass on a real Safari at the chosen floor, recorded here, is enough:
|
|
149
|
+
|
|
150
|
+
```text
|
|
151
|
+
1. showPopover() / hidePopover() in manual mode
|
|
152
|
+
2. a closed panel is genuinely invisible and unfocusable
|
|
153
|
+
3. the DOM parent is unchanged before, during and after opening
|
|
154
|
+
4. inherited theme, density and custom properties survive opening
|
|
155
|
+
5. a surface inside <dialog open> and inside a modal dialog
|
|
156
|
+
6. reopening during the exit transition
|
|
157
|
+
7. a context menu at pointer coordinates
|
|
158
|
+
8. the sheet scrim covers and intercepts the click
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Record the result in this section:
|
|
162
|
+
|
|
163
|
+
```text
|
|
164
|
+
Verified manually on:
|
|
165
|
+
Safari __._ / macOS __
|
|
166
|
+
iOS Safari __._ / iOS __ (if reachable)
|
|
167
|
+
Date: ____-__-__
|
|
168
|
+
Scenarios: _/8 passed
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Scenario 5 deserves the most attention. On Chromium the native top layer solved
|
|
172
|
+
cleanly the problem that motivated `getSurfaceRoot()` in the first place — a
|
|
173
|
+
modal dialog inerts the rest of the document — and that result is what makes
|
|
174
|
+
the transport worth making mandatory. Scenario 2 is the second: the old
|
|
175
|
+
transport used `[hidden]`, which `isElementVisible()` reads in its first
|
|
176
|
+
branch, and the migration shifts the guard onto `checkVisibility()` with
|
|
177
|
+
`getClientRects()` behind it. Which branch actually runs depends on what the
|
|
178
|
+
bottom of the new floor implements, and the third has never been exercised.
|
|
179
|
+
|
|
180
|
+
## Changelog framing for the raise
|
|
181
|
+
|
|
182
|
+
State what stops working before what it buys, with versions named. Something
|
|
183
|
+
along these lines:
|
|
184
|
+
|
|
185
|
+
> **JavaScript browser floor raised** to Safari 17+, Firefox 125+ and
|
|
186
|
+
> Chromium 116+. Interactive surfaces now use the native manual Popover
|
|
187
|
+
> transport (`popover`, `showPopover()`, `hidePopover()`) instead of
|
|
188
|
+
> physically reparenting author DOM. This preserves inherited themes, density
|
|
189
|
+
> and application custom properties while leaving Actual's dismissal, focus
|
|
190
|
+
> and positioning contracts unchanged.
|
|
191
|
+
>
|
|
192
|
+
> Browsers below this JavaScript floor no longer receive the supported full
|
|
193
|
+
> Actual runtime. Core HTML/CSS degradation remains governed separately by the
|
|
194
|
+
> Degraded tier.
|
|
195
|
+
|
|
196
|
+
Not "Popover is now required": that reads as a dependency on the whole API
|
|
197
|
+
family, when the requirement is the four primitives listed above.
|
|
198
|
+
|
|
199
|
+
## Migration checklist
|
|
200
|
+
|
|
201
|
+
For the release that raises Minimal. Items 4–7 are scaffolding built for a floor
|
|
202
|
+
that will no longer exist; a gate outliving its reason is silent dead weight, so
|
|
203
|
+
they belong here rather than being found later.
|
|
204
|
+
|
|
205
|
+
1. Pass the WebKit verification above and record it. This is the go/no-go.
|
|
206
|
+
2. Raise Minimal to the required subset above. The justification is the defect
|
|
207
|
+
and the transparent migration, not a download count: npm figures conflate
|
|
208
|
+
CI, bots, caches and the author, and say nothing about the end browser.
|
|
209
|
+
3. Delete the `Intermediate` tier. It is not merely less useful — with a new
|
|
210
|
+
Minimal above it on all three engines it is arithmetically absorbed, so its
|
|
211
|
+
features become floor-guaranteed and no information is lost.
|
|
212
|
+
4. Reclassify those features (`color-mix()`, `oklch()/oklab()`, `dvh/svh/lvh`,
|
|
213
|
+
`scrollbar-gutter`, `scrollbar-width`, `scrollbar-color`) as Minimal
|
|
214
|
+
baseline and drop their `check-compat` entries.
|
|
215
|
+
5. Remove the `@supports selector(:popover-open)` gates in `flyout.css` and
|
|
216
|
+
`tooltip.css`.
|
|
217
|
+
6. Remove the two `popover` entries from `check-compat`'s feature table.
|
|
218
|
+
7. Remove the associated `compat-ok:` pragmas, and `hasPragma()` itself if
|
|
219
|
+
nothing else consumes it.
|
|
220
|
+
8. Move `surface.js` to the manual transport, with no fallback branch.
|
|
221
|
+
9. Drop `test.failing` from the reparenting tests in
|
|
222
|
+
`tests/browser/surface-inherited-context.test.js`, keeping the assertions.
|
|
223
|
+
10. Update [surface-reparenting](surface-reparenting.md) from a known limitation
|
|
224
|
+
to resolved.
|
|
225
|
+
|
|
226
|
+
Steps 8 and 9 are linked but not enforced. The `test.failing` markers guarantee
|
|
227
|
+
that step 8 becomes unavoidable — they turn red as unexpected passes the moment
|
|
228
|
+
the transport changes — but nothing forces step 9. The test file points at the
|
|
229
|
+
note, and the note points back here; that is as far as documentation can close
|
|
230
|
+
the loop.
|
|
231
|
+
|
|
232
|
+
## What was deliberately left out
|
|
233
|
+
|
|
234
|
+
Not attempted, and not because it looked hard:
|
|
235
|
+
|
|
236
|
+
- **`popover="auto"`.** It cannot express `data-flyout-auto-close`, whose
|
|
237
|
+
default closes on an inside click; native light-dismiss closes on outside
|
|
238
|
+
clicks only, and `false` is inexpressible altogether. Adopting it would be a
|
|
239
|
+
behavior change to the component, not a transport swap.
|
|
240
|
+
- **Nested surfaces.** `openSurface()` closes every other open surface in the
|
|
241
|
+
document; the runtime is strictly flat and the transport change does not
|
|
242
|
+
alter that. Nesting is a separate feature decision, and the choice between
|
|
243
|
+
extending Actual's own stack and adopting the native one is functional before
|
|
244
|
+
it is technical.
|
|
245
|
+
- **`popover="hint"`, interest invokers, CSS anchor positioning.** Watch.
|
|
246
|
+
- **Further deletion for its own sake.** The branch's value is that it changes
|
|
247
|
+
the transport and nothing else. Broadening it would cost exactly the property
|
|
248
|
+
that makes it usable as a migration.
|
|
@@ -330,3 +330,7 @@ Prior Actual CSS libraries:
|
|
|
330
330
|
- https://getbootstrap.com/docs/5.3/components/scrollspy/
|
|
331
331
|
- https://una.im/scroll-target-group/
|
|
332
332
|
- https://www.sarasoueidan.com/blog/css-scrollspy/
|
|
333
|
+
|
|
334
|
+
### @container
|
|
335
|
+
|
|
336
|
+
- https://ishadeed.com/article/css-container-query-guide/
|