actual-css 0.9.1 → 0.9.2
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 +20 -0
- package/README.md +2 -1
- package/dist/actual.css +16 -3
- package/dist/actual.full.css +22 -3
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/dist/actual.min.css +1 -1
- package/docs/pages/components/table.md +3 -2
- package/docs/pages/examples/overview.md +1 -0
- package/docs/pages/forms/input-policy.md +59 -0
- package/docs/pages/forms/validation.md +37 -1
- package/docs/pages/foundations/tokens.md +22 -3
- package/docs/pages/foundations/typography.md +10 -0
- package/docs/pages/guides/introduction.md +2 -1
- package/docs/pages/guides/progressive-enhancement.md +3 -2
- package/package.json +2 -1
- package/src/css/core/base.css +2 -0
- package/src/css/core/tokens.css +2 -0
- package/src/css/core/variants.css +12 -3
- package/src/css/forms/form.css +6 -0
- package/src/js/validation.js +31 -0
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-normal: 400; --font-weight-medium: 500; --font-weight-semibold: 600; --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); --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: var(--font-size-md); --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-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, [data-theme] { --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); } :where([data-theme]:not([data-theme="light"]):not([data-theme="dark"])) { --primary-soft-fg: initial; --secondary-soft-fg: initial; --success-soft-fg: initial; --warning-soft-fg: initial; --danger-soft-fg: initial; --neutral-soft-fg: initial; } :root, [data-theme="light"], [data-theme="dark"] { --secondary-soft-fg: color-mix(in oklab, var(--text) 10%, var(--secondary) 90%); --success-soft-fg: color-mix(in oklab, var(--text) 15%, var(--success) 85%); --warning-soft-fg: color-mix(in oklab, var(--text) 25%, var(--warning) 75%); --danger-soft-fg: color-mix(in oklab, var(--text) 15%, var(--danger) 85%); --neutral-soft-fg: color-mix(in oklab, var(--text) 20%, var(--neutral) 80%); } :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(--hr-space, var(--space-60)); margin-inline: 0; } code, kbd, samp, pre { font-family: var(--font-mono); } pre { overflow-x: auto; } 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); --intent-soft-fg: var(--primary-soft-fg); } .secondary { --intent: var(--secondary); --intent-fg: var(--secondary-fg); --intent-soft-fg: var(--secondary-soft-fg); } .success { --intent: var(--success); --intent-fg: var(--success-fg); --intent-soft-fg: var(--success-soft-fg); } .warning { --intent: var(--warning); --intent-fg: var(--warning-fg); --intent-soft-fg: var(--warning-soft-fg); } .danger { --intent: var(--danger); --intent-fg: var(--danger-fg); --intent-soft-fg: var(--danger-soft-fg); } .neutral { --intent: var(--neutral); --intent-fg: var(--neutral-fg); --intent-soft-fg: var(--neutral-soft-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); } .compact { --gap: var(--space-20); --density-space: var(--space-20); --control-size: var(--control-size-sm); } .spacious { --gap: var(--space-50); --density-space: var(--space-50); --control-size: var(--control-size-lg); } :where( .btn, .input, .textarea, .select, .color, .file, .field, .choice, .choice-card, .input-icon, .floating-field, .otp, .join, .pagination, .tabs, .tab, .flyout ).sm { --control-size: var(--control-size-sm); --control-font-size: var(--font-size-sm); --control-pad-x: var(--font-size-sm); } :where( .btn, .input, .textarea, .select, .color, .file, .field, .choice, .choice-card, .input-icon, .floating-field, .otp, .join, .pagination, .tabs, .tab, .flyout ).lg { --control-size: var(--control-size-lg); --control-font-size: var(--font-size-lg); --control-pad-x: var(--font-size-lg); } @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: var( --intent-soft-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, [data-theme][data-theme], 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; --font-width: 100%; --font-width-dense: 95%; --line-height: 1.5; --line-height-tight: 1.25; --line-height-relaxed: 1.75; --font-weight-normal: 400; --font-weight-medium: 500; --font-weight-semibold: 600; --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); --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: var(--font-size-md); --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-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, [data-theme] { --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); } :where([data-theme]:not([data-theme="light"]):not([data-theme="dark"])) { --primary-soft-fg: initial; --secondary-soft-fg: initial; --success-soft-fg: initial; --warning-soft-fg: initial; --danger-soft-fg: initial; --neutral-soft-fg: initial; } :root, [data-theme="light"], [data-theme="dark"] { --secondary-soft-fg: color-mix(in oklab, var(--text) 10%, var(--secondary) 90%); --success-soft-fg: color-mix(in oklab, var(--text) 15%, var(--success) 85%); --warning-soft-fg: color-mix(in oklab, var(--text) 25%, var(--warning) 75%); --danger-soft-fg: color-mix(in oklab, var(--text) 15%, var(--danger) 85%); --neutral-soft-fg: color-mix(in oklab, var(--text) 20%, var(--neutral) 80%); } :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); font-stretch: var(--font-width); 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(--hr-space, var(--space-60)); margin-inline: 0; } code, kbd, samp, pre { font-family: var(--font-mono); } pre { overflow-x: auto; } 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); --intent-soft-fg: var(--primary-soft-fg); } .secondary { --intent: var(--secondary); --intent-fg: var(--secondary-fg); --intent-soft-fg: var(--secondary-soft-fg); } .success { --intent: var(--success); --intent-fg: var(--success-fg); --intent-soft-fg: var(--success-soft-fg); } .warning { --intent: var(--warning); --intent-fg: var(--warning-fg); --intent-soft-fg: var(--warning-soft-fg); } .danger { --intent: var(--danger); --intent-fg: var(--danger-fg); --intent-soft-fg: var(--danger-soft-fg); } .neutral { --intent: var(--neutral); --intent-fg: var(--neutral-fg); --intent-soft-fg: var(--neutral-soft-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); } .compact { --gap: var(--space-20); --density-space: var(--space-20); --control-size: var(--control-size-sm); font-stretch: var(--font-width-dense); } .spacious { --gap: var(--space-50); --density-space: var(--space-50); --control-size: var(--control-size-lg); font-stretch: var(--font-width); } :where( .btn, .input, .textarea, .select, .color, .file, .field, .choice, .choice-card, .input-icon, .floating-field, .otp, .join, .pagination, .tabs, .tab, .flyout ).sm { --control-size: var(--control-size-sm); --control-font-size: var(--font-size-sm); --control-pad-x: var(--font-size-sm); } :where( .btn, .input, .textarea, .select, .color, .file, .field, .choice, .choice-card, .input-icon, .floating-field, .otp, .join, .pagination, .tabs, .tab, .flyout ).lg { --control-size: var(--control-size-lg); --control-font-size: var(--font-size-lg); --control-pad-x: var(--font-size-lg); } @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: var( --intent-soft-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, [data-theme][data-theme], body { color-scheme: light; background: #fff; color: #000; } h1, h2, h3, h4, h5, h6 { break-after: avoid; } }
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
| ---------------- | --------- | ------------------------------------------------------------ |
|
|
17
17
|
| `.table-wrap` | Component | Scroll container and outer chrome (surface, border, radius). |
|
|
18
18
|
| `.table` | Component | The table itself; row and cell rules. |
|
|
19
|
-
| `.table.compact` | Density | Tighter cell padding;
|
|
19
|
+
| `.table.compact` | Density | Tighter cell padding; font size and icons unchanged. |
|
|
20
20
|
|
|
21
21
|
## Basic usage
|
|
22
22
|
|
|
@@ -101,7 +101,8 @@ table width before the wrapper scrolls horizontally.
|
|
|
101
101
|
## Compact tables
|
|
102
102
|
|
|
103
103
|
`.compact` lowers cell padding and establishes compact density for controls or
|
|
104
|
-
spacing helpers inside cells.
|
|
104
|
+
spacing helpers inside cells. Font size and icons stay unchanged; the framework's
|
|
105
|
+
denser font width may apply when the typeface supports it. It is the
|
|
105
106
|
same vocabulary as `.card.compact`.
|
|
106
107
|
|
|
107
108
|
```html demo
|
|
@@ -30,6 +30,7 @@ Whole pages, composed the way a real product would.
|
|
|
30
30
|
- [Settings modal](../../demo/templates/settings-modal.html) - two-pane preferences dialog, with the local CSS it needs audited rule by rule
|
|
31
31
|
- [Workspace settings](../../demo/templates/settings-pricing.html) - plans with visible native choices, preferences and a local segmented tab treatment
|
|
32
32
|
- [Card compositions](../../demo/templates/card-compositions.html) - editorial, team, sign-in and horizontal cards composed from existing primitives
|
|
33
|
+
- [Register form](../../demo/templates/register-form.html) - registration page with choice cards, masks, the password toggle, `tel-prefix` validation, an OTP verification dialog, and status feedback
|
|
33
34
|
|
|
34
35
|
### Layout references
|
|
35
36
|
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
> Native input intent first. Actual's runtime filters values only when explicitly asked, and fixed-shape masks stay opt-in through `data-mask`.
|
|
4
4
|
|
|
5
|
+
**Related terms:** telephone, phone, tel-prefix.
|
|
6
|
+
|
|
5
7
|
`inputmode` changes the virtual keyboard but does not constrain the value. Actual
|
|
6
8
|
does not filter plain `inputmode` fields by default because numeric entry often
|
|
7
9
|
needs temporary, signed, grouped, or locale-specific values while the user types.
|
|
@@ -89,3 +91,60 @@ Use `data-mask` when the input has a fixed shape. Tokens are `9` for a digit,
|
|
|
89
91
|
|
|
90
92
|
Masks enforce shape, not domain validity. Use application validation when a date
|
|
91
93
|
must reject impossible values such as `2026-13-40`.
|
|
94
|
+
|
|
95
|
+
### Telephone input
|
|
96
|
+
|
|
97
|
+
Do not mask telephone numbers by default. Phone formatting varies by country,
|
|
98
|
+
device, copied source, and user convention. Preserve the entered value, apply
|
|
99
|
+
only coarse client-side policy, and normalize or validate the actual number on
|
|
100
|
+
the server.
|
|
101
|
+
|
|
102
|
+
`tel-prefix` is that coarse policy: a local number passes, and an explicit
|
|
103
|
+
international number must match one of the allowed prefixes. It analyzes a copy
|
|
104
|
+
of the value and never rewrites it.
|
|
105
|
+
|
|
106
|
+
```html demo
|
|
107
|
+
<form class="needs-validation stack" data-enhance="validation" novalidate>
|
|
108
|
+
<label class="field">
|
|
109
|
+
<span class="field-label">Phone</span>
|
|
110
|
+
<input
|
|
111
|
+
class="input"
|
|
112
|
+
type="tel"
|
|
113
|
+
name="phone"
|
|
114
|
+
inputmode="tel"
|
|
115
|
+
autocomplete="tel"
|
|
116
|
+
data-validation-rules="tel-prefix +32"
|
|
117
|
+
aria-describedby="phone-help phone-error"
|
|
118
|
+
/>
|
|
119
|
+
<span class="field-help" id="phone-help">
|
|
120
|
+
Local number or +32 international number.
|
|
121
|
+
</span>
|
|
122
|
+
<span class="field-error" id="phone-error">
|
|
123
|
+
Use a local number or an allowed international prefix.
|
|
124
|
+
</span>
|
|
125
|
+
</label>
|
|
126
|
+
|
|
127
|
+
<div class="form-actions">
|
|
128
|
+
<button class="btn primary" type="submit">Submit</button>
|
|
129
|
+
<button class="btn neutral outline" type="reset">Reset</button>
|
|
130
|
+
</div>
|
|
131
|
+
</form>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
| Input | `tel-prefix +32` |
|
|
135
|
+
| -------------------- | ---------------- |
|
|
136
|
+
| `0470 12 34 56` | passes |
|
|
137
|
+
| `02/123.45.67` | passes |
|
|
138
|
+
| `+32 470 12 34 56` | passes |
|
|
139
|
+
| `0032 470 12 34 56` | passes |
|
|
140
|
+
| `(+32) 470 12 34 56` | passes |
|
|
141
|
+
| `0033 6 12 34 56 78` | fails |
|
|
142
|
+
| `+33 6 12 34 56 78` | fails |
|
|
143
|
+
|
|
144
|
+
A local number passes in any usual notation, but its digits are required: once
|
|
145
|
+
the separators are ignored, letters and stray symbols fail, so a pasted word is
|
|
146
|
+
never read as a local number. An explicit international number is normalized
|
|
147
|
+
for comparison — `0032…`, `(+32)…`, and `+32 (0)…` all read as `+32` — and a
|
|
148
|
+
foreign prefix fails. With no prefix, only local input passes. This is an origin
|
|
149
|
+
policy, not phone validation: region detection, number semantics, and E.164
|
|
150
|
+
normalization stay on the server. See [validation](validation.md).
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Inline error feedback that respects assistive technology and form state.
|
|
4
4
|
|
|
5
|
-
**Related terms:** inline validation, field error.
|
|
5
|
+
**Related terms:** inline validation, field error, telephone input.
|
|
6
6
|
|
|
7
7
|
## Class reference
|
|
8
8
|
|
|
@@ -162,10 +162,46 @@ Custom rules live in `data-validation-rules` as a comma-separated list; each rul
|
|
|
162
162
|
- `number` — Value is empty or a valid `Number`.
|
|
163
163
|
- `digits` — Value is empty or ASCII digits only.
|
|
164
164
|
- `alnum` — Value is empty or letters and digits only.
|
|
165
|
+
- `starts-with <token>…` — Value must begin with one of the literal tokens (`starts-with INV- CR-`). The value is trimmed before comparison; the match is case-sensitive.
|
|
166
|
+
- `ends-with <token>…` — Value must end with one of the literal tokens (`ends-with .pdf .csv`). Trimmed, case-sensitive.
|
|
165
167
|
- `date` — Value is a valid date in `yyyy-mm-dd`, `dd/mm/yyyy`, `mm/dd/yyyy`, `dd.mm.yyyy`, `mm.dd.yyyy`, `dd-mm-yyyy`, or `mm-dd-yyyy` shape; rejects non-existent dates like `2026-02-29`. ISO remains the recommended wire format because servers can parse it without guessing.
|
|
168
|
+
- `tel-prefix <prefix>…` — A local number (its digits, with the usual separators `space ( ) . / -` ignored) passes; an explicit international number is normalized (`00` to `+`) and must match one of the literal prefixes (`tel-prefix +32`, `tel-prefix +32 +33 +352`). A value that is not dialing digits fails. With no prefix, only local input passes. The entered value is never modified.
|
|
169
|
+
|
|
170
|
+
`starts-with` and `ends-with` options are literal tokens: no quoting, no escaping, and a token cannot contain whitespace. Without a token they fail rather than passing silently. `tel-prefix` without a prefix is instead a deliberate local-only policy.
|
|
166
171
|
|
|
167
172
|
Add your own with `FormValidator.registerRule(name, (value, el, ...opts) => boolean)`. The built-in rules treat empty values as valid so optional fields stay optional; custom rules should do the same when that behavior is wanted.
|
|
168
173
|
|
|
174
|
+
```html demo
|
|
175
|
+
<form class="needs-validation stack" data-enhance="validation" novalidate>
|
|
176
|
+
<label class="field">
|
|
177
|
+
<span class="field-label">Invoice reference</span>
|
|
178
|
+
<input class="input" name="reference"
|
|
179
|
+
data-validation-rules="starts-with INV- CR-"
|
|
180
|
+
aria-describedby="reference-help reference-error"
|
|
181
|
+
required />
|
|
182
|
+
<span class="field-help" id="reference-help">Start with INV- or CR-.</span>
|
|
183
|
+
<span class="field-error" id="reference-error">Use an INV- or CR- reference.</span>
|
|
184
|
+
</label>
|
|
185
|
+
|
|
186
|
+
<label class="field">
|
|
187
|
+
<span class="field-label">Attachment filename</span>
|
|
188
|
+
<input class="input" name="attachment"
|
|
189
|
+
data-validation-rules="ends-with .pdf .csv"
|
|
190
|
+
aria-describedby="attachment-help attachment-error"
|
|
191
|
+
required />
|
|
192
|
+
<span class="field-help" id="attachment-help">A .pdf or .csv filename.</span>
|
|
193
|
+
<span class="field-error" id="attachment-error">Choose a .pdf or .csv file.</span>
|
|
194
|
+
</label>
|
|
195
|
+
|
|
196
|
+
<div class="form-actions">
|
|
197
|
+
<button class="btn primary" type="submit">Submit</button>
|
|
198
|
+
<button class="btn neutral outline" type="reset">Reset</button>
|
|
199
|
+
</div>
|
|
200
|
+
</form>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
More specific telephone policies — determining the actual region, validating number semantics, or producing E.164 — belong in application or server code, registered through `FormValidator.registerRule()`.
|
|
204
|
+
|
|
169
205
|
The `date` rule pairs naturally with `data-mask` — the mask structures input, the rule validates meaning. Same rule, different formats:
|
|
170
206
|
|
|
171
207
|
```html demo
|
|
@@ -211,6 +211,8 @@ Typography tokens cover the document baseline and common component needs.
|
|
|
211
211
|
:root {
|
|
212
212
|
--font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
213
213
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
214
|
+
--font-width: 100%;
|
|
215
|
+
--font-width-dense: 95%;
|
|
214
216
|
|
|
215
217
|
--line-height: 1.5;
|
|
216
218
|
--font-weight-normal: 400;
|
|
@@ -264,24 +266,41 @@ Disabled controls and disabled-prone components read `--disabled-opacity` for th
|
|
|
264
266
|
|
|
265
267
|
### Density
|
|
266
268
|
|
|
267
|
-
Density contexts swap shared spacing and geometry
|
|
269
|
+
Density contexts swap shared spacing and geometry, plus a slight typographic
|
|
270
|
+
width bonus:
|
|
268
271
|
|
|
269
272
|
```css
|
|
273
|
+
:root {
|
|
274
|
+
--font-width: 100%;
|
|
275
|
+
--font-width-dense: 95%;
|
|
276
|
+
}
|
|
277
|
+
|
|
270
278
|
.compact {
|
|
271
279
|
--gap: var(--space-20);
|
|
272
280
|
--density-space: var(--space-20);
|
|
273
281
|
--control-size: var(--control-size-sm);
|
|
282
|
+
font-stretch: var(--font-width-dense);
|
|
274
283
|
}
|
|
275
284
|
|
|
276
285
|
.spacious {
|
|
277
286
|
--gap: var(--space-50);
|
|
278
287
|
--density-space: var(--space-50);
|
|
279
288
|
--control-size: var(--control-size-lg);
|
|
289
|
+
font-stretch: var(--font-width);
|
|
280
290
|
}
|
|
281
291
|
```
|
|
282
292
|
|
|
283
|
-
Density
|
|
284
|
-
|
|
293
|
+
Density never changes font size or icon size — it may only use the denser font
|
|
294
|
+
width. `font-stretch` uses a variable `wdth` axis when available, or
|
|
295
|
+
participates in normal width-face matching for families that expose multiple
|
|
296
|
+
widths. Layout must never depend on the narrowing being available. Compact
|
|
297
|
+
density may slightly narrow variable typefaces when they support a width axis.
|
|
298
|
+
This is a density enhancement, not an overflow strategy: never condense a
|
|
299
|
+
component just because its label overflows at some viewport — that hides a
|
|
300
|
+
composition problem. A theme whose typeface has no useful width axis opts out
|
|
301
|
+
with `--font-width-dense: 100%`.
|
|
302
|
+
|
|
303
|
+
Participation is opt-in and selective: controls consume `--control-size`
|
|
285
304
|
for geometry; spacing helpers and rhythms consume `--density-space` and `--gap`;
|
|
286
305
|
components whose size is intrinsic (badge, avatar, spinner, rating, key, prose,
|
|
287
306
|
inline choice/switch) keep their own scales. A local component size overrides
|
|
@@ -113,6 +113,16 @@ the document) and `--font-mono` (for `code` / `kbd` / `samp` / `pre`). There is
|
|
|
113
113
|
theme-level token in your own theme when a project actually needs one; the
|
|
114
114
|
baseline stays sans so app UI inherits predictably.
|
|
115
115
|
|
|
116
|
+
### Font width
|
|
117
|
+
|
|
118
|
+
Two public hooks carry the typographic side of density: `--font-width` (normal
|
|
119
|
+
width) and `--font-width-dense` (compact width). The document reads
|
|
120
|
+
`--font-width` through `font-stretch`; `.compact` applies
|
|
121
|
+
`--font-width-dense` and `.spacious` restores `--font-width`. A theme whose
|
|
122
|
+
typeface has no useful width axis sets `--font-width-dense: 100%`. Width is a
|
|
123
|
+
density token, not a responsive escape hatch — never condense text to fix an
|
|
124
|
+
overflowing label.
|
|
125
|
+
|
|
116
126
|
## Font weights
|
|
117
127
|
|
|
118
128
|
The framework exposes a small set of weights:
|
|
@@ -80,7 +80,8 @@ shared by buttons, badges, alerts, and cards; `.inverted` is a shared surface
|
|
|
80
80
|
modifier that paints any block with the inverse surface. `.ghost` and `.link`
|
|
81
81
|
are button-only. Size variants `.sm` and `.lg` scale typography and
|
|
82
82
|
participating component geometry consistently. Density contexts `.compact` and
|
|
83
|
-
`.spacious` change geometry and spacing without changing
|
|
83
|
+
`.spacious` change geometry and spacing without changing font size or icons;
|
|
84
|
+
`.compact` may slightly narrow typefaces that support a width axis.
|
|
84
85
|
Undocumented `is-*` classes are runtime internals.
|
|
85
86
|
|
|
86
87
|
```html
|
|
@@ -785,8 +785,9 @@ document.addEventListener("actual:invalid", (event) => {
|
|
|
785
785
|
|
|
786
786
|
Custom rules go in `data-validation-rules` and resolve through
|
|
787
787
|
`FormValidator.registerRule`. Built-in rules are `same`, `number`, `digits`,
|
|
788
|
-
`alnum`, and `
|
|
789
|
-
errors flow back through
|
|
788
|
+
`alnum`, `starts-with`, `ends-with`, `date`, and `tel-prefix`. The library never
|
|
789
|
+
performs network validation; server errors flow back through
|
|
790
|
+
`FormValidator.setErrors(form, { name: message })` and
|
|
790
791
|
`FormValidator.clearFieldError(field)`.
|
|
791
792
|
|
|
792
793
|
```js
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "actual-css",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "Plain CSS component framework with semantic classes, universal variants, small tokens, theme hooks, and progressive enhancements.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "bun@1.4.2",
|
|
@@ -144,6 +144,7 @@
|
|
|
144
144
|
"probe": "bun scripts/probe.js",
|
|
145
145
|
"serve": "bun scripts/serve.js",
|
|
146
146
|
"check:sync": "bun scripts/check-sync.js",
|
|
147
|
+
"release:notes": "bun scripts/release-notes.js",
|
|
147
148
|
"test": "bun test",
|
|
148
149
|
"test:changed": "bun test --changed=master",
|
|
149
150
|
"build:all": "bun run lint && bun run build:dist && bun run build:js && bun run build:size && bun run format:docs && bun run build:docs && bun run check:docs && bun run check:doc-classes && bun run check:compat && bun run check:css-api && bun run check:color-space && bun run check:architecture && bun run check:enhance && bun run generate:reserved && bun run check:reserved && bun run generate:catalog && bun run check:catalog && bun run check:sync && bun run check:links && bun run check:templates && bun run check:markup && bun run test",
|
package/src/css/core/base.css
CHANGED
|
@@ -7,12 +7,14 @@
|
|
|
7
7
|
* Public hooks:
|
|
8
8
|
* --link
|
|
9
9
|
* --hr-space
|
|
10
|
+
* --font-width / --font-width-dense (see variants.css for the dense application)
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
body {
|
|
13
14
|
background: var(--surface);
|
|
14
15
|
color: var(--text);
|
|
15
16
|
font-family: var(--font-sans);
|
|
17
|
+
font-stretch: var(--font-width);
|
|
16
18
|
line-height: var(--line-height);
|
|
17
19
|
}
|
|
18
20
|
|
package/src/css/core/tokens.css
CHANGED
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
/* Typography */
|
|
20
20
|
--font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
21
21
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
22
|
+
--font-width: 100%;
|
|
23
|
+
--font-width-dense: 95%;
|
|
22
24
|
--line-height: 1.5;
|
|
23
25
|
--line-height-tight: 1.25;
|
|
24
26
|
--line-height-relaxed: 1.75;
|
|
@@ -16,9 +16,16 @@
|
|
|
16
16
|
* components/alert.css — .alert default soft behind explicit --ui-*
|
|
17
17
|
*
|
|
18
18
|
* .compact/.spacious set inherited density tokens: spacing rhythm and geometry
|
|
19
|
-
* only. Density never changes
|
|
20
|
-
* --control-size; non-control components may
|
|
21
|
-
* context should affect their local rhythm.
|
|
19
|
+
* only, plus a slight typographic width bonus. Density never changes font size
|
|
20
|
+
* or icon size. Controls consume --control-size; non-control components may
|
|
21
|
+
* consume --density-* when the context should affect their local rhythm.
|
|
22
|
+
* .compact applies --font-width-dense through font-stretch (a variable wdth
|
|
23
|
+
* axis when available, otherwise normal width-face matching); layout must
|
|
24
|
+
* never depend on the narrowing being available. .spacious restores
|
|
25
|
+
* --font-width, so a spacious island inside a compact context returns to the
|
|
26
|
+
* normal width exactly as it restores its own gap, spacing, and control size.
|
|
27
|
+
* Themes whose typeface has no useful width axis opt out with
|
|
28
|
+
* --font-width-dense: 100%.
|
|
22
29
|
*
|
|
23
30
|
* .sm/.lg are local size roles, not shared measurements. Shared control
|
|
24
31
|
* participants below map both typography and geometry; components with
|
|
@@ -83,12 +90,14 @@
|
|
|
83
90
|
--gap: var(--space-20);
|
|
84
91
|
--density-space: var(--space-20);
|
|
85
92
|
--control-size: var(--control-size-sm);
|
|
93
|
+
font-stretch: var(--font-width-dense);
|
|
86
94
|
}
|
|
87
95
|
|
|
88
96
|
.spacious {
|
|
89
97
|
--gap: var(--space-50);
|
|
90
98
|
--density-space: var(--space-50);
|
|
91
99
|
--control-size: var(--control-size-lg);
|
|
100
|
+
font-stretch: var(--font-width);
|
|
92
101
|
}
|
|
93
102
|
|
|
94
103
|
/* Shared control scale. Wrappers are listed when their descendants form one
|
package/src/css/forms/form.css
CHANGED
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
*/
|
|
19
19
|
.field {
|
|
20
20
|
display: grid;
|
|
21
|
+
|
|
22
|
+
/* why: a field stretched by a taller row sibling — the classic case is a
|
|
23
|
+
neighbour revealing its .field-error — would otherwise hand the extra
|
|
24
|
+
block size to its auto rows and inflate the control. Rows keep their
|
|
25
|
+
natural height; the slack stays at the end. */
|
|
26
|
+
align-content: start;
|
|
21
27
|
gap: var(--space-10);
|
|
22
28
|
color: var(--text);
|
|
23
29
|
font-size: var(--control-font-size);
|
package/src/js/validation.js
CHANGED
|
@@ -86,9 +86,40 @@ const rules = {
|
|
|
86
86
|
alnum(v) {
|
|
87
87
|
return /^[a-z0-9]+$/i.test(v);
|
|
88
88
|
},
|
|
89
|
+
// Options are literal tokens: no quoting, no escaping, no whitespace. A
|
|
90
|
+
// missing option is a configuration error and fails rather than passing
|
|
91
|
+
// silently. Comparison is case-sensitive; the value is trimmed to compare.
|
|
92
|
+
"starts-with"(v, _el, ...prefixes) {
|
|
93
|
+
const value = v.trim();
|
|
94
|
+
return prefixes.length > 0 && prefixes.some((prefix) => value.startsWith(prefix));
|
|
95
|
+
},
|
|
96
|
+
"ends-with"(v, _el, ...suffixes) {
|
|
97
|
+
const value = v.trim();
|
|
98
|
+
return suffixes.length > 0 && suffixes.some((suffix) => value.endsWith(suffix));
|
|
99
|
+
},
|
|
89
100
|
date(v) {
|
|
90
101
|
return isValidDate(v);
|
|
91
102
|
},
|
|
103
|
+
// Telephone origin policy, not telephone validation. A local number carries
|
|
104
|
+
// nothing but its digits after the usual separators are ignored, so a value
|
|
105
|
+
// with letters or stray symbols is not a number and fails instead of reading
|
|
106
|
+
// as local. Local numbering still belongs to the application and the server;
|
|
107
|
+
// an explicit international number must match an allowed prefix. No prefixes
|
|
108
|
+
// means local-only: local input passes, any explicit international notation
|
|
109
|
+
// fails. The entered value is analyzed, never rewritten.
|
|
110
|
+
"tel-prefix"(value, _el, ...prefixes) {
|
|
111
|
+
let v = value.trim().replace(/[\s()./-]+/g, "");
|
|
112
|
+
if (v.startsWith("00")) {
|
|
113
|
+
v = `+${v.slice(2)}`;
|
|
114
|
+
}
|
|
115
|
+
if (!/^\+?\d+$/.test(v)) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
if (!v.startsWith("+")) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
return prefixes.some((prefix) => v.startsWith(prefix));
|
|
122
|
+
},
|
|
92
123
|
};
|
|
93
124
|
|
|
94
125
|
function isFormElement(el) {
|