actual-css 0.3.1 → 0.4.1

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 (223) hide show
  1. package/CHANGELOG.md +109 -3
  2. package/README.md +35 -30
  3. package/dist/actual.css +66 -4958
  4. package/dist/actual.full.css +7548 -0
  5. package/dist/actual.full.js +1 -0
  6. package/dist/actual.full.min.css +1 -0
  7. package/dist/actual.js +1 -1
  8. package/dist/actual.min.css +1 -1
  9. package/docs/design-notes/app-ui.md +33 -0
  10. package/docs/design-notes/cascade-layer.md +34 -13
  11. package/docs/design-notes/column-layout.md +677 -0
  12. package/docs/design-notes/enhancement-contract.md +25 -0
  13. package/docs/design-notes/forced-colors.md +3 -0
  14. package/docs/design-notes/grid.md +785 -0
  15. package/docs/design-notes/naming.md +15 -0
  16. package/docs/design-notes/references.md +1 -1
  17. package/docs/design-notes/widget-primitives.md +1 -0
  18. package/docs/navigation.json +12 -2
  19. package/docs/pages/components/alert.md +21 -14
  20. package/docs/pages/components/app-navigation.md +34 -0
  21. package/docs/pages/components/aura.md +1 -1
  22. package/docs/pages/components/avatar.md +16 -6
  23. package/docs/pages/components/badge.md +9 -7
  24. package/docs/pages/components/button.md +46 -16
  25. package/docs/pages/components/card.md +17 -9
  26. package/docs/pages/components/chat.md +8 -8
  27. package/docs/pages/components/dialog.md +7 -7
  28. package/docs/pages/components/drawer.md +3 -3
  29. package/docs/pages/components/fab.md +9 -7
  30. package/docs/pages/components/indicator.md +52 -0
  31. package/docs/pages/components/list.md +62 -0
  32. package/docs/pages/components/navbar.md +15 -8
  33. package/docs/pages/components/pagination.md +4 -3
  34. package/docs/pages/components/rating.md +68 -0
  35. package/docs/pages/components/skeleton.md +4 -4
  36. package/docs/pages/components/spinner.md +5 -5
  37. package/docs/pages/components/steps.md +57 -0
  38. package/docs/pages/components/table.md +30 -5
  39. package/docs/pages/enhancements/flyout.md +15 -15
  40. package/docs/pages/enhancements/overview.md +1 -1
  41. package/docs/pages/enhancements/scrollspy.md +6 -6
  42. package/docs/pages/enhancements/status-bar.md +5 -1
  43. package/docs/pages/enhancements/tabs.md +7 -8
  44. package/docs/pages/enhancements/tooltip.md +1 -1
  45. package/docs/pages/examples/overview.md +47 -22
  46. package/docs/pages/forms/choice-cards.md +11 -6
  47. package/docs/pages/forms/controls.md +1 -1
  48. package/docs/pages/forms/detached-actions.md +1 -1
  49. package/docs/pages/forms/floating-field.md +10 -5
  50. package/docs/pages/forms/form-actions.md +1 -1
  51. package/docs/pages/forms/input-policy.md +1 -1
  52. package/docs/pages/forms/joined-controls.md +4 -4
  53. package/docs/pages/forms/labels.md +6 -6
  54. package/docs/pages/forms/otp.md +33 -1
  55. package/docs/pages/forms/overview.md +19 -19
  56. package/docs/pages/forms/password.md +1 -1
  57. package/docs/pages/forms/range.md +1 -1
  58. package/docs/pages/forms/states-and-sizes.md +44 -48
  59. package/docs/pages/forms/sticky-actions.md +3 -3
  60. package/docs/pages/forms/switch.md +110 -2
  61. package/docs/pages/forms/validation.md +9 -9
  62. package/docs/pages/foundations/philosophy.md +1 -1
  63. package/docs/pages/foundations/theming.md +17 -2
  64. package/docs/pages/foundations/tokens.md +22 -10
  65. package/docs/pages/foundations/typography.md +50 -20
  66. package/docs/pages/guides/bootstrap.md +11 -11
  67. package/docs/pages/guides/integrating-actual-css.md +192 -0
  68. package/docs/pages/guides/introduction.md +23 -26
  69. package/docs/pages/guides/modular-import.md +173 -0
  70. package/docs/pages/guides/progressive-enhancement.md +81 -78
  71. package/docs/pages/guides/tailwind.md +6 -8
  72. package/docs/pages/layout/app-layout.md +57 -0
  73. package/docs/pages/layout/app-shell.md +28 -7
  74. package/docs/pages/layout/center.md +12 -1
  75. package/docs/pages/layout/choosing.md +54 -0
  76. package/docs/pages/layout/column-layout.md +206 -0
  77. package/docs/pages/layout/frame.md +6 -1
  78. package/docs/pages/layout/grid.md +133 -102
  79. package/docs/pages/layout/media.md +1 -1
  80. package/docs/pages/layout/overview.md +15 -2
  81. package/docs/pages/layout/scroll-snap.md +2 -2
  82. package/docs/pages/layout/scroll-target.md +45 -0
  83. package/docs/pages/layout/sidebar-layout.md +38 -18
  84. package/docs/pages/layout/stack.md +70 -1
  85. package/docs/pages/layout/switcher.md +10 -26
  86. package/docs/pages/layout/topbar.md +2 -2
  87. package/docs/pages/patterns/actions.md +1 -1
  88. package/docs/pages/patterns/code-block.md +1 -1
  89. package/docs/pages/patterns/exclusive-accordion.md +1 -1
  90. package/docs/pages/patterns/hierarchical-nav.md +1 -1
  91. package/docs/pages/patterns/icon-input.md +1 -1
  92. package/docs/pages/patterns/icon-slot.md +61 -0
  93. package/docs/pages/patterns/intent-nesting.md +1 -1
  94. package/docs/pages/patterns/navigation-list.md +1 -1
  95. package/docs/pages/patterns/overview.md +1 -1
  96. package/docs/pages/patterns/structured-lists.md +38 -40
  97. package/docs/pages/utilities/accessibility.md +3 -3
  98. package/docs/pages/utilities/flex.md +15 -15
  99. package/docs/pages/utilities/links.md +4 -4
  100. package/docs/pages/utilities/list.md +8 -8
  101. package/docs/pages/utilities/overflow.md +8 -8
  102. package/docs/pages/utilities/overview.md +1 -1
  103. package/docs/pages/utilities/shape.md +3 -3
  104. package/docs/pages/utilities/sizing-wrapping.md +23 -16
  105. package/docs/pages/utilities/spacing.md +20 -20
  106. package/docs/pages/utilities/text.md +10 -10
  107. package/llms.txt +46 -4
  108. package/package.json +43 -13
  109. package/scripts/reserved-classes.json +278 -0
  110. package/src/cli/actual-css.js +133 -0
  111. package/src/css/actual.css +1 -31
  112. package/src/css/actual.full.css +11 -2
  113. package/src/css/components/alert.css +17 -4
  114. package/src/css/components/app-nav.css +76 -0
  115. package/src/css/components/avatar.css +4 -0
  116. package/src/css/components/badge.css +19 -19
  117. package/src/css/components/breadcrumb.css +1 -1
  118. package/src/css/components/button.css +20 -32
  119. package/src/css/components/card.css +34 -9
  120. package/src/css/{optional → components}/chat.css +0 -6
  121. package/src/css/components/drawer.css +24 -5
  122. package/src/css/{optional → components}/fab.css +1 -7
  123. package/src/css/components/flyout.css +5 -0
  124. package/src/css/components/index.css +37 -27
  125. package/src/css/components/indicator.css +47 -0
  126. package/src/css/components/join.css +8 -1
  127. package/src/css/components/key.css +2 -0
  128. package/src/css/components/list.css +83 -0
  129. package/src/css/components/menu.css +11 -8
  130. package/src/css/components/meter.css +4 -1
  131. package/src/css/components/modal.css +5 -17
  132. package/src/css/components/navbar.css +6 -15
  133. package/src/css/components/pagination.css +11 -0
  134. package/src/css/components/rating.css +101 -0
  135. package/src/css/components/spinner.css +5 -0
  136. package/src/css/components/status-bar.css +5 -0
  137. package/src/css/components/steps.css +122 -0
  138. package/src/css/components/surface.css +5 -0
  139. package/src/css/components/tab.css +20 -22
  140. package/src/css/components/table.css +6 -0
  141. package/src/css/components/tooltip.css +3 -7
  142. package/src/css/{focus.css → core/focus.css} +0 -8
  143. package/src/css/core/index.css +9 -0
  144. package/src/css/core/print.css +18 -0
  145. package/src/css/{theme.css → core/theme.css} +19 -19
  146. package/src/css/{tokens.css → core/tokens.css} +16 -4
  147. package/src/css/{variants.css → core/variants.css} +3 -2
  148. package/src/css/{optional → effects}/aura.css +1 -10
  149. package/src/css/effects/index.css +2 -0
  150. package/src/css/forms/base.css +8 -0
  151. package/src/css/forms/choice-card.css +7 -15
  152. package/src/css/forms/choice.css +49 -37
  153. package/src/css/forms/control.css +4 -1
  154. package/src/css/forms/custom-select.css +2 -0
  155. package/src/css/{optional → forms}/floating-field.css +25 -7
  156. package/src/css/forms/index.css +10 -13
  157. package/src/css/forms/native.css +15 -2
  158. package/src/css/{optional → forms}/otp.css +25 -16
  159. package/src/css/forms/range.css +14 -4
  160. package/src/css/forms/switch.css +31 -29
  161. package/src/css/layout/app-layout.css +102 -0
  162. package/src/css/layout/app-shell.css +6 -0
  163. package/src/css/layout/center.css +14 -0
  164. package/src/css/layout/cluster.css +21 -0
  165. package/src/css/layout/column-layout.css +144 -0
  166. package/src/css/layout/container-query.css +15 -0
  167. package/src/css/layout/frame.css +19 -0
  168. package/src/css/layout/grid.css +155 -0
  169. package/src/css/layout/index.css +44 -0
  170. package/src/css/layout/measure.css +9 -0
  171. package/src/css/layout/media.css +10 -0
  172. package/src/css/layout/scroll-target.css +15 -0
  173. package/src/css/layout/scroller.css +41 -0
  174. package/src/css/layout/sidebar-layout.css +52 -0
  175. package/src/css/layout/stack.css +21 -0
  176. package/src/css/layout/switcher.css +23 -0
  177. package/src/css/{optional/layout-extra.css → layout/topbar.css} +0 -20
  178. package/src/css/typography/index.css +5 -0
  179. package/src/css/typography/lead.css +7 -0
  180. package/src/css/{components → typography}/overline.css +2 -2
  181. package/src/css/{prose.css → typography/prose.css} +57 -38
  182. package/src/css/{utilities.css → utilities/base.css} +2 -0
  183. package/src/css/utilities/index.css +3 -0
  184. package/src/js/escape.js +36 -0
  185. package/src/js/flyout.js +22 -12
  186. package/src/js/full.js +26 -0
  187. package/src/js/index.js +5 -21
  188. package/src/js/surface.js +1 -31
  189. package/src/js/tooltip.js +48 -19
  190. package/src/js/validation.js +1 -1
  191. package/src/tooling/css-bundle.js +308 -0
  192. package/dist/actual-themes.min.css +0 -1
  193. package/dist/optional.css +0 -1048
  194. package/dist/optional.min.css +0 -1
  195. package/docs/pages/guides/optional-components.md +0 -33
  196. package/docs/pages/layout/list.md +0 -41
  197. package/src/css/grid.css +0 -109
  198. package/src/css/layout.css +0 -123
  199. package/src/css/optional/index.css +0 -10
  200. package/src/css/optional/scroller.css +0 -63
  201. package/src/css/print.css +0 -47
  202. package/src/css/themes/corporate.css +0 -60
  203. package/src/css/themes/cyberpunk.css +0 -60
  204. package/src/css/themes/dim.css +0 -33
  205. package/src/css/themes/edge.css +0 -117
  206. package/src/css/themes/forest.css +0 -65
  207. package/src/css/themes/gradient.css +0 -97
  208. package/src/css/themes/index.css +0 -47
  209. package/src/css/themes/indigo.css +0 -33
  210. package/src/css/themes/lavender.css +0 -66
  211. package/src/css/themes/mono.css +0 -60
  212. package/src/css/themes/neon.css +0 -66
  213. package/src/css/themes/ocean.css +0 -60
  214. package/src/css/themes/petrol.css +0 -64
  215. package/src/css/themes/spruce.css +0 -64
  216. package/src/css/themes/square.css +0 -65
  217. package/src/css/themes/sunset.css +0 -66
  218. /package/src/css/{base.css → core/base.css} +0 -0
  219. /package/src/css/{intents.css → core/intents.css} +0 -0
  220. /package/src/css/{reset.css → core/reset.css} +0 -0
  221. /package/src/css/{optional → layout}/scroll-snap.css +0 -0
  222. /package/src/css/{optional/typography-fluid.css → typography/fluid.css} +0 -0
  223. /package/src/css/{optional/utilities-extra.css → utilities/extra.css} +0 -0
@@ -0,0 +1,41 @@
1
+ /*
2
+ * Scroller — optional scrollbar treatment for overflow regions.
3
+ *
4
+ * Apply to any scroll container (.overflow-auto, sidebars, long tables). Only
5
+ * the standard properties are used: the engine keeps drawing the scrollbar,
6
+ * this hands it the theme's density and colour. No ::-webkit-scrollbar chrome —
7
+ * rebuilding a thumb by hand means re-implementing the hover, the corner and
8
+ * the light/dark adaptation that color-scheme already gives for free, and the
9
+ * two never match anyway.
10
+ *
11
+ * Above the capability floor: scrollbar-color lands in Chromium 121 and
12
+ * Safari 18.2, the floor is Chromium 99 / Safari 15.4. Older engines ignore
13
+ * both declarations and draw their native scrollbar — the intended fallback,
14
+ * not a broken state.
15
+ *
16
+ * Styles the scrollbar only; it does not create the overflow. Pair it with
17
+ * .overflow-auto or a component that already scrolls.
18
+ *
19
+ * Public hooks:
20
+ * --scroller-track
21
+ * --scroller-thumb
22
+ */
23
+ .scroller {
24
+ --scroller-track: transparent;
25
+ --scroller-thumb: var(--border);
26
+
27
+ scrollbar-color: var(--scroller-thumb) var(--scroller-track);
28
+ scrollbar-width: thin;
29
+ }
30
+
31
+ /* Opt-in gutter: reserves space so the layout does not shift when
32
+ the scrollbar appears. Use alongside a known-overflow container. */
33
+ .scroller.stable-gutter {
34
+ scrollbar-gutter: stable;
35
+ }
36
+
37
+ @supports (color: color-mix(in oklch, red, white)) {
38
+ .scroller {
39
+ --scroller-thumb: color-mix(in oklch, var(--text-muted), transparent 55%);
40
+ }
41
+ }
@@ -0,0 +1,52 @@
1
+ /*
2
+ * Main content + secondary region.
3
+ *
4
+ * The children wrap according to the space allocated to this layout, without
5
+ * an ancestor opt-in or viewport threshold. The large growth ratio lets the
6
+ * main region reclaim the full row when the aside wraps instead of remaining
7
+ * at its minimum viable width.
8
+ *
9
+ * Public hooks:
10
+ * --sidebar-content-min
11
+ * --sidebar-layout-size
12
+ *
13
+ * Child contract: the main region is first and the aside second by default
14
+ * (aside on the inline end). .reverse expects the aside first in the DOM, so
15
+ * visual, reading and keyboard order all agree — the role rules swap so the
16
+ * aside still takes its preferred width and the main still reclaims the row.
17
+ */
18
+ .sidebar-layout {
19
+ display: flex;
20
+ flex-wrap: wrap;
21
+ gap: var(--gap);
22
+ align-items: flex-start;
23
+ }
24
+
25
+ /* Main region — flexible, reclaims the full row when the aside wraps. */
26
+ .sidebar-layout > :first-child {
27
+ flex-basis: 0;
28
+ flex-grow: 999;
29
+ min-inline-size: min(100%, var(--sidebar-content-min, 30rem));
30
+ }
31
+
32
+ /* Aside region — preferred width, never forces content min-width. */
33
+ .sidebar-layout > :last-child {
34
+ flex-basis: var(--sidebar-layout-size, 18rem);
35
+ flex-grow: 1;
36
+ min-inline-size: 0;
37
+ }
38
+
39
+ /* .reverse: the aside is first in the DOM and the main last, so each role
40
+ rule above would hit the wrong element. Swap the sizing per child; the
41
+ higher-specificity reverse rules outrank the positional defaults. */
42
+ .sidebar-layout.reverse > :last-child {
43
+ flex-basis: 0;
44
+ flex-grow: 999;
45
+ min-inline-size: min(100%, var(--sidebar-content-min, 30rem));
46
+ }
47
+
48
+ .sidebar-layout.reverse > :first-child {
49
+ flex-basis: var(--sidebar-layout-size, 18rem);
50
+ flex-grow: 1;
51
+ min-inline-size: 0;
52
+ }
@@ -0,0 +1,21 @@
1
+ /* Vertical flow. .stack owns the block axis only: block margins reset, inline
2
+ margins stay free (self-centering children via margin-inline: auto keep
3
+ working).
4
+ Child contract: direct children become flex items; margins reset, spacing
5
+ comes from --gap.
6
+
7
+ --gap is inherited, so a container that composes other primitives sets its
8
+ own rhythm on the `gap` property, not on the token: setting --gap on a
9
+ region re-spaces every nested stack, cluster and grid at any depth. Setting
10
+ the token stays correct on the element that consumes it, and .sm/.lg set it
11
+ deliberately so a density context reaches nested layouts. See
12
+ docs/pages/layout/stack.md. */
13
+ .stack {
14
+ display: flex;
15
+ flex-direction: column;
16
+ gap: var(--gap);
17
+ }
18
+
19
+ .stack > * {
20
+ margin-block: 0;
21
+ }
@@ -0,0 +1,23 @@
1
+ /*
2
+ * Peer regions that switch as a group.
3
+ *
4
+ * All children sit side by side when they all fit; otherwise every child
5
+ * stacks. Unlike .grid, this recipe cannot create a partial row such as two
6
+ * items plus one orphan.
7
+ *
8
+ * Public hooks:
9
+ * --switcher-threshold
10
+ *
11
+ * Child contract: every direct child is a peer; the flex-basis applies to all.
12
+ */
13
+ .switcher {
14
+ display: flex;
15
+ flex-wrap: wrap;
16
+ gap: var(--gap);
17
+ }
18
+
19
+ .switcher > * {
20
+ flex-basis: calc((var(--switcher-threshold, 40rem) - 100%) * 999);
21
+ flex-grow: 1;
22
+ min-inline-size: 0;
23
+ }
@@ -1,23 +1,3 @@
1
- /*
2
- * Additional layout primitives not included in the core bundle.
3
- *
4
- * Import this file explicitly when a project needs these patterns.
5
- *
6
- * Public hooks:
7
- * --switcher-threshold
8
- */
9
- .switcher {
10
- display: flex;
11
- flex-wrap: wrap;
12
- gap: var(--gap);
13
- }
14
-
15
- .switcher > * {
16
- flex-basis: calc((var(--switcher-threshold, 40rem) - 100%) * 999);
17
- flex-grow: 1;
18
- min-inline-size: 0;
19
- }
20
-
21
1
  /*
22
2
  * Sticky, frosted top bar shell for app/dashboard shells (persistent
23
3
  * sidebar + topbar layouts). Owns positioning, stacking, and the frosted
@@ -0,0 +1,5 @@
1
+ /* Typography — prose scope and typographic treatments. */
2
+ @import "./prose.css";
3
+ @import "./lead.css";
4
+ @import "./overline.css";
5
+ @import "./fluid.css";
@@ -0,0 +1,7 @@
1
+ /* Lead paragraph: a slightly larger, more relaxed reading-introduction text.
2
+ Non-fluid and always available — use .text-lead from the optional fluid
3
+ module for marketing/display surfaces. */
4
+ .lead {
5
+ font-size: 1.125em;
6
+ line-height: var(--line-height-relaxed);
7
+ }
@@ -42,7 +42,7 @@
42
42
  keeps a readable bordered pill everywhere else. */
43
43
  @supports (color: color-mix(in oklch, red, white)) {
44
44
  .overline.pill {
45
- border-color: color-mix(in oklch, var(--intent, var(--border)) 35%, var(--border));
46
- background: color-mix(in oklch, var(--intent, transparent) 6%, var(--surface));
45
+ border-color: color-mix(in oklab, var(--intent, var(--border)) 35%, var(--border));
46
+ background: color-mix(in oklab, var(--intent, transparent) 6%, var(--surface));
47
47
  }
48
48
  }
@@ -14,6 +14,7 @@
14
14
  * --prose-h2
15
15
  * --prose-h3
16
16
  * --prose-h4
17
+ * --link
17
18
  */
18
19
 
19
20
  .prose {
@@ -29,19 +30,27 @@
29
30
  --prose-h4: 1.125rem;
30
31
 
31
32
  max-inline-size: var(--prose-measure);
32
- color: var(--text);
33
33
  font-size: var(--prose-size);
34
34
  line-height: var(--prose-line-height);
35
35
  overflow-wrap: break-word;
36
36
  }
37
37
 
38
- .prose :where(p, ul, ol, dl, blockquote, pre, table, figure) {
39
- margin-block: var(--prose-flow) 0;
38
+ /* Vertical rhythm belongs to the direct block that participates in the
39
+ document, so it must not be injected into the internals of a nested
40
+ component (e.g. a .table-wrap around a .table would trap the margin). */
41
+ .prose > * {
42
+ margin-block-end: 0;
43
+ }
44
+
45
+ .prose > * + * {
46
+ margin-block-start: var(--prose-flow);
40
47
  }
41
48
 
49
+ /* Heading hierarchy remains low-specificity and contextual: a surface may
50
+ remap --heading without prose repainting it with the root text color. */
42
51
  .prose :where(h1, h2, h3, h4, h5, h6) {
43
52
  margin-block: 1.5em 0;
44
- color: var(--text);
53
+ color: var(--heading, var(--text));
45
54
  font-weight: var(--font-weight-strong);
46
55
  line-height: var(--prose-heading-line-height);
47
56
  }
@@ -54,48 +63,71 @@
54
63
  margin-block-start: 0;
55
64
  }
56
65
 
57
- .prose h1 {
66
+ /* Element recipes are wrapped in :where() so every .prose rule is 0-1-0.
67
+ An explicit component/util class can therefore override prose defaults
68
+ regardless of the underlying element — a .btn or .alert link must not be
69
+ reshaped just because it lives inside authored content. */
70
+ .prose :where(h1) {
58
71
  font-size: var(--prose-h1);
59
72
  }
60
73
 
61
- .prose h2 {
74
+ .prose :where(h2) {
62
75
  font-size: var(--prose-h2);
63
76
  }
64
77
 
65
- .prose h3 {
78
+ .prose :where(h3) {
66
79
  font-size: var(--prose-h3);
67
80
  }
68
81
 
69
- .prose h4 {
82
+ .prose :where(h4) {
70
83
  font-size: var(--prose-h4);
71
84
  }
72
85
 
73
- .prose a {
74
- color: var(--primary);
86
+ /* Link ink defaults to --primary. --link is an optional escape hatch: a
87
+ theme whose primary is too light or too vivid to read as inline text sets
88
+ it without having to restate the rule. */
89
+ .prose :where(a) {
90
+ color: var(--link, var(--primary));
75
91
  text-decoration-line: underline;
76
92
  text-decoration-thickness: 0.08em;
77
93
  text-underline-offset: 0.18em;
78
94
  }
79
95
 
80
- .prose a:hover {
96
+ .prose :where(a:hover) {
81
97
  text-decoration-thickness: 0.12em;
82
98
  }
83
99
 
84
100
  @supports (color: color-mix(in oklch, red, white)) {
85
- .prose a:hover {
86
- color: color-mix(in oklch, var(--primary), var(--text) 15%);
101
+ .prose :where(a:hover) {
102
+ color: color-mix(in oklab, var(--link, var(--primary)), var(--text) 15%);
87
103
  }
88
104
  }
89
105
 
106
+ /* Rich-text descendants are intentional here. Components and application UI
107
+ belong outside the prose boundary. */
90
108
  .prose :where(ul, ol) {
91
109
  padding-inline-start: 1.5em;
92
110
  }
93
111
 
94
- .prose li + li {
112
+ .prose :where(li + li) {
95
113
  margin-block-start: 0.35em;
96
114
  }
97
115
 
98
- .prose code {
116
+ /* Description lists keep document flow: a valid <dl> may pair several <dt>
117
+ with one <dd> or the reverse, so no grid layout is imposed here. */
118
+ .prose :where(dt) {
119
+ font-weight: var(--font-weight-strong);
120
+ }
121
+
122
+ .prose :where(dd) {
123
+ margin-inline-start: 1.5em;
124
+ }
125
+
126
+ .prose :where(dd + dt) {
127
+ margin-block-start: 0.75em;
128
+ }
129
+
130
+ .prose :where(code) {
99
131
  padding: 0.15em 0.3em;
100
132
  border-radius: var(--radius-sm);
101
133
  background: var(--surface-subtle);
@@ -104,7 +136,8 @@
104
136
  }
105
137
 
106
138
  /* Visual twin of .key (components/key.css) — keep the two recipes in sync. */
107
- .prose kbd {
139
+ /* @sync key-recipe */
140
+ .prose :where(kbd) {
108
141
  display: inline-flex;
109
142
  align-items: center;
110
143
  justify-content: center;
@@ -123,8 +156,9 @@
123
156
  white-space: nowrap;
124
157
  vertical-align: 0.08em;
125
158
  }
159
+ /* @sync key-recipe */
126
160
 
127
- .prose pre {
161
+ .prose :where(pre) {
128
162
  overflow-x: auto;
129
163
  padding: 1em;
130
164
  border: var(--border-width) solid var(--border);
@@ -132,53 +166,38 @@
132
166
  background: var(--surface-subtle);
133
167
  }
134
168
 
135
- .prose pre code {
169
+ .prose :where(pre) :where(code) {
136
170
  padding: 0;
137
171
  background: transparent;
138
172
  }
139
173
 
140
- .prose blockquote {
174
+ .prose :where(blockquote) {
141
175
  padding-inline-start: 1em;
142
176
  border-inline-start: var(--border-width) solid var(--border);
143
177
  color: var(--text-muted);
144
178
  }
145
179
 
146
- .prose figure {
180
+ .prose :where(figure) {
147
181
  margin-inline: 0;
148
182
  }
149
183
 
150
- .prose figcaption {
184
+ .prose :where(figcaption) {
151
185
  margin-block-start: 0.5em;
152
186
  color: var(--text-muted);
153
187
  font-size: 0.875em;
154
188
  }
155
189
 
156
- .prose table {
190
+ .prose :where(table) {
157
191
  inline-size: 100%;
158
192
  border-collapse: collapse;
159
193
  }
160
194
 
161
- .prose th,
162
- .prose td {
195
+ .prose :where(th, td) {
163
196
  padding: 0.5em;
164
197
  border-block-end: var(--border-width) solid var(--border);
165
198
  text-align: start;
166
199
  }
167
200
 
168
- /* Lead paragraph: a slightly larger, more relaxed reading-introduction text.
169
- Non-fluid and always available — use .text-lead from the optional fluid
170
- module for marketing/display surfaces. */
171
- .lead {
172
- font-size: 1.125em;
173
- line-height: var(--line-height-relaxed);
174
- }
175
-
176
- /* Readable content measure. Composes with .center for centering or with
177
- margin-inline: auto for a one-off centered block. */
178
- .measure {
179
- max-inline-size: var(--prose-measure, 70ch);
180
- }
181
-
182
201
  .prose.sm {
183
202
  --prose-size: 0.9375rem;
184
203
  --prose-h1: 1.75rem;
@@ -12,6 +12,8 @@
12
12
  margin: -1px;
13
13
  overflow: hidden;
14
14
  clip: rect(0, 0, 0, 0);
15
+ /* clip alone is legacy-only; clip-path is the modern hiding mechanism. */
16
+ clip-path: inset(50%);
15
17
  white-space: nowrap;
16
18
  border: 0;
17
19
  }
@@ -0,0 +1,3 @@
1
+ /* Utilities — small, single-purpose escape hatches. */
2
+ @import "./base.css";
3
+ @import "./extra.css";
@@ -0,0 +1,36 @@
1
+ /*
2
+ * Per-document LIFO dismissal for transient UI that closes on Escape.
3
+ * Consumers register only while visible, so the topmost dismissable UI owns
4
+ * one key press and pinned or hidden UI never consumes it.
5
+ */
6
+
7
+ const dismissableStacks = new WeakMap();
8
+
9
+ export function registerEscapeDismissal(element, dismiss) {
10
+ const doc = element.ownerDocument;
11
+ let stack = dismissableStacks.get(doc);
12
+ if (!stack) {
13
+ stack = [];
14
+ dismissableStacks.set(doc, stack);
15
+ doc.addEventListener("keydown", onDocumentEscape);
16
+ }
17
+
18
+ const entry = { element, dismiss };
19
+ stack.push(entry);
20
+
21
+ return () => {
22
+ const index = stack.indexOf(entry);
23
+ if (index >= 0) stack.splice(index, 1);
24
+ };
25
+ }
26
+
27
+ function onDocumentEscape(event) {
28
+ if (event.key !== "Escape" || event.ctrlKey || event.altKey || event.shiftKey) return;
29
+
30
+ const stack = dismissableStacks.get(event.currentTarget);
31
+ const entry = stack.at(-1);
32
+ if (!entry) return;
33
+
34
+ event.preventDefault();
35
+ entry.dismiss({ restoreFocus: true });
36
+ }
package/src/js/flyout.js CHANGED
@@ -144,10 +144,29 @@ function connectTrigger(trigger) {
144
144
  trigger.addEventListener("keydown", onTriggerKeydown, { signal: controller.signal });
145
145
  }
146
146
 
147
- function resolvePanel(trigger, state) {
148
- if (state.panel?.isConnected) return state.panel;
147
+ function releasePanelReference(trigger, state) {
148
+ state.releaseMenu?.();
149
+ state.releaseMenu = null;
150
+
151
+ const panel = state.panel;
152
+ state.panel = null;
153
+ if (!panel) return;
154
+
155
+ const panelEntry = panelRefs.get(panel);
156
+ if (!panelEntry) return;
157
+
158
+ panelEntry.triggers.delete(trigger);
159
+ if (panelEntry.triggers.size === 0) {
160
+ panelEntry.release();
161
+ panelRefs.delete(panel);
162
+ }
163
+ }
149
164
 
165
+ function resolvePanel(trigger, state) {
150
166
  const panel = panelFor(trigger);
167
+ if (state.panel === panel && panel?.isConnected) return panel;
168
+
169
+ releasePanelReference(trigger, state);
151
170
  if (!panel) return null;
152
171
 
153
172
  let panelEntry = panelRefs.get(panel);
@@ -174,21 +193,12 @@ function resolvePanel(trigger, state) {
174
193
  function disconnectTrigger(trigger) {
175
194
  const state = triggerMap.get(trigger);
176
195
  if (!state) return;
177
- state.releaseMenu?.();
178
196
  state.controller.abort();
179
197
  if (state.panel?.isConnected && isSurfaceOpen(state.panel)) {
180
198
  closeSurface(state.panel, { restoreFocus: false });
181
199
  }
200
+ releasePanelReference(trigger, state);
182
201
  triggerMap.delete(trigger);
183
-
184
- const panelEntry = state.panel ? panelRefs.get(state.panel) : undefined;
185
- if (panelEntry) {
186
- panelEntry.triggers.delete(trigger);
187
- if (panelEntry.triggers.size === 0) {
188
- panelEntry.release();
189
- panelRefs.delete(state.panel);
190
- }
191
- }
192
202
  }
193
203
 
194
204
  registerEnhancement("flyout", (trigger) => {
package/src/js/full.js ADDED
@@ -0,0 +1,26 @@
1
+ /*
2
+ * Actual CSS - JavaScript runtime, full bundle entry.
3
+ *
4
+ * Each module self-registers via observer: importing this bundle enables
5
+ * common behaviors. No DOMContentLoaded ceremony, no init calls.
6
+ * Injected content wires automatically; removal cleans up automatically.
7
+ *
8
+ * A plain `<script src="actual.full.js"></script>` (no bundler, no import) still
9
+ * gets the full runtime, including declarative triggers like status's
10
+ * `command="--status"` and the `actual:status` event — no global needed to
11
+ * drive them from other inline scripts either.
12
+ */
13
+
14
+ import "./flyout.js";
15
+ import "./context-menu.js";
16
+ import "./dialog.js";
17
+ import "./dismiss.js";
18
+ import "./tab.js";
19
+ import "./tooltip.js";
20
+ import "./scrollspy.js";
21
+ import "./filter.js";
22
+ import "./mask.js";
23
+ import "./password.js";
24
+ import "./validation.js";
25
+ import "./status.js";
26
+ import "./index.js";
package/src/js/index.js CHANGED
@@ -1,28 +1,12 @@
1
1
  /*
2
- * Actual CSS - JavaScript runtime.
2
+ * Actual CSS - JavaScript runtime, core entry.
3
3
  *
4
- * Each module self-registers via observer: importing the default runtime
5
- * enables common behaviors. No DOMContentLoaded ceremony, no init calls.
6
- * Injected content wires automatically; removal cleans up automatically.
7
- *
8
- * A plain `<script src="actual.js"></script>` (no bundler, no import) still
9
- * gets the full runtime, including declarative triggers like status's
10
- * `command="--status"` and the `actual:status` event — no global needed to
11
- * drive them from other inline scripts either.
4
+ * This entry only starts the enhancement manifest loader. Built-in behaviors
5
+ * (dialogs, menus, tabs, validation, ...) ship in the full bundle entry
6
+ * (src/js/full.js). Use this module for pages that only need progressive
7
+ * enhancement via enhancement manifests.
12
8
  */
13
9
 
14
- import "./flyout.js";
15
- import "./context-menu.js";
16
- import "./dialog.js";
17
- import "./dismiss.js";
18
- import "./tab.js";
19
- import "./tooltip.js";
20
- import "./scrollspy.js";
21
- import "./filter.js";
22
- import "./mask.js";
23
- import "./password.js";
24
- import "./validation.js";
25
- import "./status.js";
26
10
  import { watchEnhancementManifests } from "./enhancement-loader.js";
27
11
 
28
12
  watchEnhancementManifests();
package/src/js/surface.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import enhance from "./enhance.js";
2
+ import { registerEscapeDismissal } from "./escape.js";
2
3
  import { EVENTS } from "./events.js";
3
4
  import { autoUpdate, reposition, repositionAt } from "./floating.js";
4
5
 
@@ -418,37 +419,6 @@ function ensureDocumentClick(menu) {
418
419
  doc.addEventListener("click", onDocumentClick);
419
420
  }
420
421
 
421
- const dismissableStacks = new WeakMap();
422
-
423
- export function registerEscapeDismissal(element, dismiss) {
424
- const doc = element.ownerDocument;
425
- let stack = dismissableStacks.get(doc);
426
- if (!stack) {
427
- stack = [];
428
- dismissableStacks.set(doc, stack);
429
- doc.addEventListener("keydown", onDocumentEscape);
430
- }
431
-
432
- const entry = { element, dismiss };
433
- stack.push(entry);
434
-
435
- return () => {
436
- const index = stack.indexOf(entry);
437
- if (index >= 0) stack.splice(index, 1);
438
- };
439
- }
440
-
441
- function onDocumentEscape(event) {
442
- if (event.key !== "Escape" || event.ctrlKey || event.altKey || event.shiftKey) return;
443
-
444
- const stack = dismissableStacks.get(event.currentTarget);
445
- const entry = stack.at(-1);
446
- if (!entry) return;
447
-
448
- event.preventDefault();
449
- entry.dismiss({ restoreFocus: true });
450
- }
451
-
452
422
  export function getSurfaceAutoClose(menu) {
453
423
  return surfaceMap.get(menu)?.autoClose ?? "outside";
454
424
  }