actual-css 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +166 -0
  4. package/dist/actual.css +4929 -0
  5. package/dist/actual.js +3 -0
  6. package/dist/actual.js.map +31 -0
  7. package/dist/actual.min.css +1 -0
  8. package/docs/README.md +26 -0
  9. package/docs/components.md +860 -0
  10. package/docs/design-notes/accessibility-decisions.md +32 -0
  11. package/docs/design-notes/browser-support.md +93 -0
  12. package/docs/design-notes/cascade-layer.md +52 -0
  13. package/docs/design-notes/css-anchor.md +33 -0
  14. package/docs/design-notes/naming.md +37 -0
  15. package/docs/design-notes/previous-work.md +5 -0
  16. package/docs/design-notes/references.md +324 -0
  17. package/docs/forms.md +1265 -0
  18. package/docs/javascript.md +426 -0
  19. package/docs/layout.md +480 -0
  20. package/docs/mental-model.md +73 -0
  21. package/docs/patterns.md +415 -0
  22. package/docs/reserved.md +7 -0
  23. package/docs/tokens.md +455 -0
  24. package/docs/typography.md +159 -0
  25. package/docs/ui.md +937 -0
  26. package/docs/utilities.md +384 -0
  27. package/llms.txt +17 -0
  28. package/package.json +101 -0
  29. package/src/css/actual.css +17 -0
  30. package/src/css/actual.full.css +2 -0
  31. package/src/css/actual.layer.css +3 -0
  32. package/src/css/base.css +70 -0
  33. package/src/css/components/accordion.css +97 -0
  34. package/src/css/components/actions.css +21 -0
  35. package/src/css/components/alert.css +137 -0
  36. package/src/css/components/avatar.css +199 -0
  37. package/src/css/components/badge.css +143 -0
  38. package/src/css/components/breadcrumb.css +56 -0
  39. package/src/css/components/busy.css +44 -0
  40. package/src/css/components/button.css +216 -0
  41. package/src/css/components/card.css +140 -0
  42. package/src/css/components/drawer.css +122 -0
  43. package/src/css/components/flyout.css +243 -0
  44. package/src/css/components/index.css +41 -0
  45. package/src/css/components/join.css +106 -0
  46. package/src/css/components/key.css +25 -0
  47. package/src/css/components/meter.css +81 -0
  48. package/src/css/components/modal.css +162 -0
  49. package/src/css/components/navbar.css +98 -0
  50. package/src/css/components/overline.css +49 -0
  51. package/src/css/components/pagination.css +30 -0
  52. package/src/css/components/progress.css +126 -0
  53. package/src/css/components/scrollspy.css +27 -0
  54. package/src/css/components/skeleton.css +67 -0
  55. package/src/css/components/spinner.css +60 -0
  56. package/src/css/components/status-bar.css +82 -0
  57. package/src/css/components/surface.css +48 -0
  58. package/src/css/components/tab.css +128 -0
  59. package/src/css/components/table.css +64 -0
  60. package/src/css/components/tooltip.css +107 -0
  61. package/src/css/dialog-fallback.css +56 -0
  62. package/src/css/focus.css +13 -0
  63. package/src/css/forms/choice-card.css +158 -0
  64. package/src/css/forms/choice.css +196 -0
  65. package/src/css/forms/control.css +106 -0
  66. package/src/css/forms/custom-select.css +93 -0
  67. package/src/css/forms/form-actions.css +43 -0
  68. package/src/css/forms/form.css +57 -0
  69. package/src/css/forms/index.css +15 -0
  70. package/src/css/forms/input-icon.css +36 -0
  71. package/src/css/forms/native.css +68 -0
  72. package/src/css/forms/select.css +36 -0
  73. package/src/css/forms/switch.css +127 -0
  74. package/src/css/forms/validation.css +63 -0
  75. package/src/css/grid.css +95 -0
  76. package/src/css/intents.css +33 -0
  77. package/src/css/layout.css +80 -0
  78. package/src/css/optional/index.css +5 -0
  79. package/src/css/optional/layout-extra.css +55 -0
  80. package/src/css/optional/scroll-snap.css +51 -0
  81. package/src/css/optional/scroller.css +51 -0
  82. package/src/css/optional/typography-fluid.css +48 -0
  83. package/src/css/optional/utilities-extra.css +289 -0
  84. package/src/css/print.css +47 -0
  85. package/src/css/prose.css +187 -0
  86. package/src/css/reset.css +56 -0
  87. package/src/css/theme.css +186 -0
  88. package/src/css/themes/corporate.css +60 -0
  89. package/src/css/themes/cyberpunk.css +60 -0
  90. package/src/css/themes/dark.css +32 -0
  91. package/src/css/themes/dim.css +33 -0
  92. package/src/css/themes/edge.css +143 -0
  93. package/src/css/themes/forest.css +65 -0
  94. package/src/css/themes/gradient.css +97 -0
  95. package/src/css/themes/index.css +43 -0
  96. package/src/css/themes/lavender.css +66 -0
  97. package/src/css/themes/mono.css +60 -0
  98. package/src/css/themes/neon.css +66 -0
  99. package/src/css/themes/ocean.css +60 -0
  100. package/src/css/themes/petrol.css +64 -0
  101. package/src/css/themes/spruce.css +64 -0
  102. package/src/css/themes/square.css +68 -0
  103. package/src/css/themes/sunset.css +66 -0
  104. package/src/css/tokens.css +122 -0
  105. package/src/css/utilities.css +162 -0
  106. package/src/css/variants.css +118 -0
  107. package/src/js/command.js +165 -0
  108. package/src/js/context-menu.js +291 -0
  109. package/src/js/dialog-fallback.js +163 -0
  110. package/src/js/dialog.js +503 -0
  111. package/src/js/enhance.js +208 -0
  112. package/src/js/events.js +22 -0
  113. package/src/js/filter.js +122 -0
  114. package/src/js/floating.js +462 -0
  115. package/src/js/flyout.js +187 -0
  116. package/src/js/focus.js +35 -0
  117. package/src/js/index.js +26 -0
  118. package/src/js/input.js +47 -0
  119. package/src/js/keys.js +30 -0
  120. package/src/js/mask.js +184 -0
  121. package/src/js/menu.js +88 -0
  122. package/src/js/password.js +98 -0
  123. package/src/js/scrollspy.js +121 -0
  124. package/src/js/selectors.js +59 -0
  125. package/src/js/status.js +158 -0
  126. package/src/js/surface.js +358 -0
  127. package/src/js/tab.js +173 -0
  128. package/src/js/tooltip.js +374 -0
  129. package/src/js/validation.js +419 -0
@@ -0,0 +1,162 @@
1
+ /*
2
+ * Small, single-purpose utilities for common escape hatches.
3
+ *
4
+ * These support components and layout primitives without becoming a
5
+ * utility-first API.
6
+ */
7
+ .sr-only {
8
+ position: absolute;
9
+ inline-size: 1px;
10
+ block-size: 1px;
11
+ padding: 0;
12
+ margin: -1px;
13
+ overflow: hidden;
14
+ clip: rect(0, 0, 0, 0);
15
+ white-space: nowrap;
16
+ border: 0;
17
+ }
18
+
19
+ .overflow-auto {
20
+ overflow: auto;
21
+ }
22
+
23
+ .fit {
24
+ inline-size: fit-content;
25
+ max-inline-size: 100%;
26
+ }
27
+
28
+ .grow {
29
+ flex: 1 1 auto;
30
+ min-inline-size: 0;
31
+ }
32
+
33
+ .nowrap {
34
+ white-space: nowrap;
35
+ }
36
+
37
+ .truncate {
38
+ min-inline-size: 0;
39
+ overflow: hidden;
40
+ text-overflow: ellipsis;
41
+ white-space: nowrap;
42
+ }
43
+
44
+ .items-start {
45
+ --items-align: start;
46
+ align-items: start;
47
+ }
48
+
49
+ .items-center {
50
+ --items-align: center;
51
+ align-items: center;
52
+ }
53
+
54
+ .items-end {
55
+ --items-align: end;
56
+ align-items: end;
57
+ }
58
+
59
+ .muted {
60
+ color: var(--text-muted);
61
+ }
62
+
63
+ /* Circle: forces a fully round shape without mutating global radius tokens.
64
+ Works on any component because utilities load after components. .join is
65
+ the one exception: it writes corner longhands on its children, so
66
+ .join.circle keeps its own --join-radius hook (join.css). */
67
+ .circle {
68
+ border-radius: var(--radius-full);
69
+ }
70
+
71
+ /* Text alignment — uses logical properties so it follows writing direction. */
72
+ .text-start {
73
+ --text-align: start;
74
+ text-align: start;
75
+ }
76
+
77
+ .text-center {
78
+ --text-align: center;
79
+ text-align: center;
80
+ }
81
+
82
+ .text-end {
83
+ --text-align: end;
84
+ text-align: end;
85
+ }
86
+
87
+ /* Text wrapping utilities. Unsupported values are ignored by the browser. */
88
+ .text-balance {
89
+ text-wrap: balance;
90
+ }
91
+
92
+ .text-pretty {
93
+ text-wrap: pretty;
94
+ }
95
+
96
+ /* Generic list chrome reset. Use for any list (tags, footer columns, related
97
+ items). For navigation lists, prefer .nav-list for semantic intent.
98
+ components/{actions,navbar}.css apply their own resets where needed. */
99
+ .list-reset {
100
+ margin: 0;
101
+ padding: 0;
102
+ list-style: none;
103
+ }
104
+
105
+ /* Separator dot for inline metadata rows (byline, timestamp, read time,
106
+ breadcrumb-like facts). Pair with a flex/cluster container; flex: none
107
+ keeps a flex parent from shrinking it to nothing. */
108
+ .dot {
109
+ flex: none;
110
+ inline-size: 0.25rem;
111
+ block-size: 0.25rem;
112
+ border-radius: var(--radius-full);
113
+ background: var(--text-subtle);
114
+ }
115
+
116
+ /* Link variants for chrome that should not look like prose links. Use in
117
+ navigation, tags, footer, tables of contents, and similar surfaces. */
118
+ .link-plain {
119
+ color: inherit;
120
+ text-decoration: none;
121
+ }
122
+
123
+ .link-muted {
124
+ color: var(--text-muted);
125
+ text-decoration: none;
126
+ }
127
+
128
+ @media (hover: hover) {
129
+ .link-muted:hover {
130
+ color: var(--text);
131
+ }
132
+ }
133
+
134
+ /* Spacing step helpers — semantic scale (sm / default / lg).
135
+ Names map to logical properties for writing-direction safety. The default
136
+ step uses --space-4, which matches the framework's built-in padding on
137
+ cards, dialogs, and form-actions. */
138
+
139
+ /* Gap */
140
+ .gap-none {
141
+ gap: 0;
142
+ }
143
+
144
+ /* Padding — block (vertical) */
145
+ .py {
146
+ padding-block: var(--variant-space);
147
+ }
148
+
149
+ /* Padding — inline (horizontal) */
150
+ .px {
151
+ padding-inline: var(--variant-space);
152
+ }
153
+
154
+ /* Margin — block-start */
155
+ .mbs {
156
+ margin-block-start: var(--variant-space);
157
+ }
158
+
159
+ /* Margin — block-end */
160
+ .mbe {
161
+ margin-block-end: var(--variant-space);
162
+ }
@@ -0,0 +1,118 @@
1
+ /*
2
+ * Cross-cutting variant token mapping.
3
+ *
4
+ * Variant scope: .solid/.soft/.outline set --ui-* for components that opt into
5
+ * shared variants. Component-specific defaults (.btn solid-by-default, .badge
6
+ * solid-by-default, .alert soft-by-default) live with their components:
7
+ * components/button.css — .btn default solid + .btn.ghost + .btn.link
8
+ * components/badge.css — .badge default solid
9
+ * components/alert.css — .alert default + .alert.solid/.alert.outline
10
+ *
11
+ * .sm/.lg set shared size tokens. Controls consume --control-*; non-control
12
+ * components may consume --variant-* when the variant should affect their
13
+ * local density. Compact components may consume --variant-compact-*.
14
+ * Components with bespoke sizing (spinner, avatar) define local .sm/.lg;
15
+ * applying .sm/.lg to card, dialog, accordion, navbar, table is
16
+ * a no-op.
17
+ *
18
+ * Intent list source of truth: intents.css. Components consume --intent* tokens
19
+ * instead of re-listing intent classes.
20
+ */
21
+ .solid {
22
+ --ui-bg: var(--intent, var(--neutral));
23
+ --ui-fg: var(--intent-fg, var(--neutral-fg));
24
+ --ui-border: transparent;
25
+ }
26
+
27
+ .soft {
28
+ --ui-bg: var(--surface-subtle);
29
+ --ui-border: var(--border);
30
+ --ui-fg: var(--intent, var(--text));
31
+ }
32
+
33
+ .outline {
34
+ --ui-bg: transparent;
35
+ --ui-fg: var(--intent, var(--text));
36
+ --ui-border: var(--intent, var(--border));
37
+ }
38
+
39
+ .sm {
40
+ --gap: var(--space-2);
41
+ --variant-space: var(--space-2);
42
+ --control-size: var(--control-size-sm);
43
+ --control-font-size: var(--font-size-sm);
44
+ --variant-font-size: var(--font-size-sm);
45
+ --variant-icon-size: 1rem;
46
+ --variant-pad-block: var(--space-3);
47
+ --variant-compact-size: 1.25rem;
48
+ --variant-compact-font-size: 0.75rem;
49
+ }
50
+ .lg {
51
+ --gap: var(--space-5);
52
+ --variant-space: var(--space-5);
53
+ --control-size: var(--control-size-lg);
54
+ --control-font-size: var(--font-size-lg);
55
+ --variant-font-size: var(--font-size-lg);
56
+ --variant-icon-size: 1.5rem;
57
+ --variant-pad-block: var(--space-5);
58
+ --variant-compact-size: 1.875rem;
59
+ --variant-compact-font-size: var(--font-size-sm);
60
+ }
61
+
62
+ /* Modern color enhancement. Baseline variant values above remain usable
63
+ everywhere. Interactive states live in the components that consume them. */
64
+ @supports (color: color-mix(in oklch, red, white)) {
65
+ .soft {
66
+ /* @sync soft-recipe */
67
+ --ui-bg: color-mix(in oklch, var(--surface) var(--soft-bg-mix), var(--intent, var(--neutral)));
68
+ --ui-border: color-mix(
69
+ in oklch,
70
+ var(--surface) var(--soft-border-mix),
71
+ var(--intent, var(--neutral))
72
+ );
73
+ /* @sync soft-recipe */
74
+ }
75
+ }
76
+
77
+ /* Interactive hover recipe shared by .btn and .badge. Declared outside :hover
78
+ on purpose: --ui-hover-bg is only consumed inside their @media (hover: hover)
79
+ rules, so these values are inert until hovered. :is() carries real
80
+ specificity so the components' :where() intent boundaries
81
+ (--ui-hover-bg: initial) cannot override the recipe. The solid/soft overlay
82
+ darkening lives in the consuming components. */
83
+ :is(.btn, a.badge, button.badge):is(.outline, .ghost) {
84
+ --ui-hover-bg: var(--surface-subtle);
85
+ }
86
+
87
+ @supports (color: color-mix(in oklch, red, white)) {
88
+ /* Solid has no literal class requirement: bare .btn/.badge default to solid,
89
+ so this must match anything that isn't one of the other variants rather
90
+ than an explicit .solid class. Stacks with the box-shadow overlay the
91
+ consuming components apply on top for extra depth. */
92
+ :is(.btn, a.badge, button.badge):not(:where(.soft, .outline, .ghost, .link)) {
93
+ --ui-hover-bg: color-mix(in oklch, var(--intent, var(--neutral)) 88%, black);
94
+ }
95
+
96
+ /* Same recipe as the resting .soft bg with the surface share reduced by
97
+ --soft-hover-alpha: stays opaque, so it reads on any backdrop instead of
98
+ letting the container show through on hover. */
99
+ :is(.btn, a.badge, button.badge):is(.soft) {
100
+ --ui-hover-bg: color-mix(
101
+ in oklch,
102
+ var(--surface) calc(var(--soft-bg-mix) - var(--soft-hover-alpha)),
103
+ var(--intent, var(--neutral))
104
+ );
105
+ }
106
+
107
+ /* Mixed against the opaque --surface-subtle rather than transparent: a
108
+ transparent-based tint reads clean on light surfaces but goes muddy and
109
+ low-contrast on dark ones, since the same low alpha lands very
110
+ differently over a light vs. a near-black backdrop. */
111
+ :is(.btn, a.badge, button.badge):is(.outline) {
112
+ --ui-hover-bg: color-mix(in oklch, var(--surface-subtle) 92%, var(--intent, var(--neutral)));
113
+ }
114
+
115
+ :is(.btn, a.badge, button.badge):is(.ghost) {
116
+ --ui-hover-bg: color-mix(in oklch, var(--intent, var(--text)) 8%, transparent);
117
+ }
118
+ }
@@ -0,0 +1,165 @@
1
+ /*
2
+ * Command — stateless command/commandfor routing.
3
+ *
4
+ * The registry tracks command handlers, never triggers or targets. One click
5
+ * listener per document reads the current DOM at event time, resolves the
6
+ * current commandfor target, and routes the action. Newly inserted triggers,
7
+ * late targets, changed commandfor values, and same-id replacements therefore
8
+ * work immediately without an observer, scan, refresh call, or per-element
9
+ * listener.
10
+ */
11
+
12
+ /* One registry per document owns its delegated listener and command handlers.
13
+ * The WeakMap does not retain discarded documents; registry entries contain
14
+ * behavior definitions only, never DOM triggers or resolved targets. */
15
+ const registries = new WeakMap();
16
+
17
+ function commandNames(commands, caller) {
18
+ const names = Array.isArray(commands) ? commands : [commands];
19
+ if (!names.length || names.some((name) => typeof name !== "string" || !name)) {
20
+ throw new TypeError(`${caller} requires one or more command names.`);
21
+ }
22
+ return [...new Set(names)];
23
+ }
24
+
25
+ // Native command keywords are ASCII case-insensitive. Custom commands keep
26
+ // their exact spelling, as required by the command invoker contract.
27
+ function commandKey(command) {
28
+ return command.startsWith("--")
29
+ ? command
30
+ : command.replace(/[A-Z]/g, (character) => character.toLowerCase());
31
+ }
32
+
33
+ /**
34
+ * Resolve a trigger's current `commandfor` target in the same document or
35
+ * shadow root.
36
+ *
37
+ * @param {Element} trigger Element carrying the `commandfor` attribute.
38
+ * @returns {Element | null} The current target, or `null` when it cannot be resolved.
39
+ */
40
+ export function targetFor(trigger) {
41
+ const id = trigger.getAttribute("commandfor");
42
+ if (!id) return null;
43
+
44
+ const root = trigger.getRootNode();
45
+ return root.getElementById?.(id) ?? null;
46
+ }
47
+
48
+ /**
49
+ * Build the button selector corresponding to one or more command names.
50
+ *
51
+ * @param {string | string[]} commands Command name or names to include.
52
+ * @returns {string} A selector matching command buttons with `commandfor`.
53
+ * @throws {TypeError} When no valid command name is provided.
54
+ */
55
+ export function commandSelector(commands) {
56
+ const attributes = commandNames(commands, "commandSelector()").map(
57
+ (name) => `[command="${name.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"]`,
58
+ );
59
+ return `button[commandfor]:is(${attributes.join(", ")})`;
60
+ }
61
+
62
+ function triggerFromEvent(event, doc) {
63
+ const ElementClass = doc.defaultView?.Element;
64
+ if (!ElementClass) return null;
65
+
66
+ for (const node of event.composedPath?.() ?? [event.target]) {
67
+ if (node instanceof ElementClass && node.matches("button[commandfor][command]")) {
68
+ return node;
69
+ }
70
+ if (node === doc) break;
71
+ }
72
+
73
+ return null;
74
+ }
75
+
76
+ function createRegistry(doc) {
77
+ const commands = new Map();
78
+
79
+ function route(event) {
80
+ if (event.defaultPrevented) return;
81
+
82
+ const trigger = triggerFromEvent(event, doc);
83
+ if (!trigger || trigger.disabled) return;
84
+
85
+ const command = commandKey(trigger.getAttribute("command"));
86
+ const registration = commands.get(command);
87
+ if (!registration) return;
88
+
89
+ const target = registration.resolve(trigger);
90
+ if (!target) return;
91
+
92
+ registration.prepare?.(trigger, target, command);
93
+ registration.handle(event, trigger, target, command);
94
+ }
95
+
96
+ doc.addEventListener("click", route);
97
+ return { commands, route };
98
+ }
99
+
100
+ /**
101
+ * Register one behavior for one or more command names.
102
+ *
103
+ * `prepare`, when present, runs immediately before `handle` on every matching
104
+ * action. It must be idempotent; it is useful for semantics derived from the
105
+ * resolved target, without introducing a connection lifecycle.
106
+ *
107
+ * @param {string | string[]} commands Command name or names owned by this behavior.
108
+ * @param {object} options Behavior callbacks.
109
+ * @param {(trigger: HTMLButtonElement) => Element | null} [options.resolve=targetFor]
110
+ * Resolver evaluated for every action.
111
+ * @param {(trigger: HTMLButtonElement, target: Element, command: string) => void} [options.prepare]
112
+ * Optional idempotent callback run immediately before the handler.
113
+ * @param {(event: MouseEvent, trigger: HTMLButtonElement, target: Element, command: string) => void} options.handle
114
+ * Command handler.
115
+ * @returns {{ disconnect: () => void }} An idempotent registration teardown handle.
116
+ * @throws {TypeError} When command names or callbacks are invalid.
117
+ * @throws {Error} When a command already has an owner in the current document.
118
+ */
119
+ export function registerCommands(commands, { resolve = targetFor, prepare, handle } = {}) {
120
+ const names = [
121
+ ...new Set(commandNames(commands, "registerCommands()").map((name) => commandKey(name))),
122
+ ];
123
+ if (
124
+ typeof resolve !== "function" ||
125
+ (prepare !== undefined && typeof prepare !== "function") ||
126
+ typeof handle !== "function"
127
+ ) {
128
+ throw new TypeError("registerCommands() requires function callbacks.");
129
+ }
130
+ if (typeof document === "undefined") return { disconnect() {} };
131
+
132
+ const doc = document;
133
+ let registry = registries.get(doc);
134
+ if (!registry) {
135
+ registry = createRegistry(doc);
136
+ registries.set(doc, registry);
137
+ }
138
+
139
+ const conflict = names.find((name) => registry.commands.has(name));
140
+ if (conflict) {
141
+ throw new Error(`Command "${conflict}" is already registered in this document.`);
142
+ }
143
+
144
+ const registration = { resolve, prepare, handle };
145
+ for (const name of names) registry.commands.set(name, registration);
146
+
147
+ let connected = true;
148
+ return {
149
+ disconnect() {
150
+ if (!connected) return;
151
+ connected = false;
152
+
153
+ for (const name of names) {
154
+ if (registry.commands.get(name) === registration) {
155
+ registry.commands.delete(name);
156
+ }
157
+ }
158
+
159
+ if (!registry.commands.size) {
160
+ doc.removeEventListener("click", registry.route);
161
+ registries.delete(doc);
162
+ }
163
+ },
164
+ };
165
+ }