actual-css 0.1.0 → 0.3.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 (215) hide show
  1. package/CHANGELOG.md +90 -1
  2. package/README.md +25 -8
  3. package/dist/actual-themes.min.css +1 -0
  4. package/dist/actual.css +1492 -931
  5. package/dist/actual.js +1 -3
  6. package/dist/actual.min.css +1 -1
  7. package/dist/optional.css +1048 -0
  8. package/dist/optional.min.css +1 -0
  9. package/docs/design-notes/browser-support.md +95 -6
  10. package/docs/design-notes/cascade-layer.md +1 -1
  11. package/docs/design-notes/enhancement-contract.md +101 -0
  12. package/docs/design-notes/forced-colors.md +105 -0
  13. package/docs/design-notes/naming.md +3 -1
  14. package/docs/design-notes/platform-alignment.md +492 -0
  15. package/docs/design-notes/references.md +8 -0
  16. package/docs/design-notes/widget-primitives.md +119 -0
  17. package/docs/navigation.json +135 -0
  18. package/docs/pages/components/accordion.md +80 -0
  19. package/docs/pages/components/alert.md +203 -0
  20. package/docs/pages/components/aura.md +35 -0
  21. package/docs/pages/components/avatar.md +95 -0
  22. package/docs/pages/components/badge.md +102 -0
  23. package/docs/pages/components/breadcrumb.md +19 -0
  24. package/docs/pages/components/button.md +205 -0
  25. package/docs/pages/components/card.md +183 -0
  26. package/docs/pages/components/chat.md +50 -0
  27. package/docs/pages/components/dialog.md +367 -0
  28. package/docs/pages/components/drawer.md +113 -0
  29. package/docs/pages/components/fab.md +118 -0
  30. package/docs/pages/components/key.md +33 -0
  31. package/docs/pages/components/meter.md +23 -0
  32. package/docs/pages/components/navbar.md +92 -0
  33. package/docs/pages/components/pagination.md +55 -0
  34. package/docs/pages/components/progress.md +29 -0
  35. package/docs/pages/components/skeleton.md +38 -0
  36. package/docs/pages/components/spinner.md +93 -0
  37. package/docs/pages/components/table.md +102 -0
  38. package/docs/pages/enhancements/flyout.md +391 -0
  39. package/docs/pages/enhancements/overview.md +36 -0
  40. package/docs/pages/enhancements/scrollspy.md +71 -0
  41. package/docs/pages/enhancements/status-bar.md +72 -0
  42. package/docs/pages/enhancements/tabs.md +80 -0
  43. package/docs/pages/enhancements/tooltip.md +130 -0
  44. package/docs/pages/examples/overview.md +41 -0
  45. package/docs/pages/forms/choice-cards.md +102 -0
  46. package/docs/pages/forms/controls.md +32 -0
  47. package/docs/pages/forms/detached-actions.md +59 -0
  48. package/docs/pages/forms/floating-field.md +88 -0
  49. package/docs/pages/forms/form-actions.md +56 -0
  50. package/docs/pages/forms/input-policy.md +91 -0
  51. package/docs/pages/forms/joined-controls.md +37 -0
  52. package/docs/pages/forms/labels.md +42 -0
  53. package/docs/pages/forms/otp.md +50 -0
  54. package/docs/pages/forms/overview.md +188 -0
  55. package/docs/pages/forms/password.md +40 -0
  56. package/docs/pages/forms/range.md +17 -0
  57. package/docs/pages/forms/states-and-sizes.md +321 -0
  58. package/docs/pages/forms/sticky-actions.md +187 -0
  59. package/docs/pages/forms/switch.md +31 -0
  60. package/docs/pages/forms/validation.md +241 -0
  61. package/docs/{mental-model.md → pages/foundations/philosophy.md} +33 -18
  62. package/docs/pages/foundations/theming.md +92 -0
  63. package/docs/pages/foundations/tokens.md +421 -0
  64. package/docs/{typography.md → pages/foundations/typography.md} +27 -9
  65. package/docs/pages/guides/bootstrap.md +438 -0
  66. package/docs/pages/guides/introduction.md +157 -0
  67. package/docs/pages/guides/optional-components.md +33 -0
  68. package/docs/pages/guides/progressive-enhancement.md +686 -0
  69. package/docs/pages/guides/tailwind.md +455 -0
  70. package/docs/pages/layout/app-shell.md +25 -0
  71. package/docs/pages/layout/center.md +29 -0
  72. package/docs/pages/layout/cluster.md +57 -0
  73. package/docs/pages/layout/frame.md +30 -0
  74. package/docs/pages/layout/grid.md +142 -0
  75. package/docs/pages/layout/list.md +41 -0
  76. package/docs/pages/layout/media.md +21 -0
  77. package/docs/pages/layout/overview.md +37 -0
  78. package/docs/pages/layout/scroll-snap.md +54 -0
  79. package/docs/pages/layout/sidebar-layout.md +27 -0
  80. package/docs/pages/layout/stack.md +18 -0
  81. package/docs/pages/layout/switcher.md +36 -0
  82. package/docs/pages/layout/topbar.md +23 -0
  83. package/docs/pages/patterns/actions.md +52 -0
  84. package/docs/pages/patterns/code-block.md +36 -0
  85. package/docs/pages/patterns/exclusive-accordion.md +16 -0
  86. package/docs/pages/patterns/hierarchical-nav.md +26 -0
  87. package/docs/pages/patterns/icon-input.md +25 -0
  88. package/docs/pages/patterns/intent-nesting.md +3 -0
  89. package/docs/pages/patterns/navigation-list.md +235 -0
  90. package/docs/pages/patterns/overview.md +58 -0
  91. package/docs/pages/patterns/structured-lists.md +47 -0
  92. package/docs/pages/utilities/accessibility.md +34 -0
  93. package/docs/pages/utilities/flex.md +105 -0
  94. package/docs/pages/utilities/links.md +18 -0
  95. package/docs/pages/utilities/list.md +31 -0
  96. package/docs/pages/utilities/overflow.md +43 -0
  97. package/docs/pages/utilities/overview.md +18 -0
  98. package/docs/pages/utilities/shape.md +25 -0
  99. package/docs/pages/utilities/sizing-wrapping.md +115 -0
  100. package/docs/pages/utilities/spacing.md +79 -0
  101. package/docs/pages/utilities/text.md +43 -0
  102. package/llms.txt +10 -4
  103. package/package.json +30 -16
  104. package/src/css/actual.css +12 -1
  105. package/src/css/base.css +3 -9
  106. package/src/css/components/accordion.css +18 -30
  107. package/src/css/components/actions.css +4 -1
  108. package/src/css/components/alert.css +162 -49
  109. package/src/css/components/avatar.css +16 -10
  110. package/src/css/components/badge.css +82 -39
  111. package/src/css/components/breadcrumb.css +7 -15
  112. package/src/css/components/busy.css +7 -0
  113. package/src/css/components/button.css +82 -38
  114. package/src/css/components/card.css +17 -37
  115. package/src/css/components/drawer.css +30 -13
  116. package/src/css/components/flyout.css +39 -110
  117. package/src/css/components/index.css +1 -0
  118. package/src/css/components/join.css +14 -3
  119. package/src/css/components/key.css +2 -0
  120. package/src/css/components/menu.css +125 -0
  121. package/src/css/components/meter.css +0 -23
  122. package/src/css/components/modal.css +162 -7
  123. package/src/css/components/navbar.css +17 -18
  124. package/src/css/components/overline.css +0 -7
  125. package/src/css/components/pagination.css +1 -1
  126. package/src/css/components/progress.css +8 -26
  127. package/src/css/components/scrollspy.css +2 -2
  128. package/src/css/components/skeleton.css +7 -0
  129. package/src/css/components/spinner.css +10 -11
  130. package/src/css/components/status-bar.css +18 -28
  131. package/src/css/components/surface.css +2 -8
  132. package/src/css/components/tab.css +20 -48
  133. package/src/css/components/table.css +7 -5
  134. package/src/css/components/tooltip.css +14 -7
  135. package/src/css/focus.css +16 -3
  136. package/src/css/forms/choice-card.css +13 -15
  137. package/src/css/forms/choice.css +21 -28
  138. package/src/css/forms/control.css +16 -26
  139. package/src/css/forms/custom-select.css +9 -11
  140. package/src/css/forms/form-actions.css +14 -3
  141. package/src/css/forms/form.css +7 -4
  142. package/src/css/forms/index.css +5 -4
  143. package/src/css/forms/input-icon.css +30 -12
  144. package/src/css/forms/native.css +48 -33
  145. package/src/css/forms/range.css +103 -0
  146. package/src/css/forms/select.css +8 -8
  147. package/src/css/forms/switch.css +14 -13
  148. package/src/css/forms/validation.css +32 -5
  149. package/src/css/grid.css +19 -18
  150. package/src/css/intents.css +2 -2
  151. package/src/css/layout.css +29 -1
  152. package/src/css/optional/aura.css +93 -0
  153. package/src/css/optional/chat.css +105 -0
  154. package/src/css/optional/fab.css +104 -0
  155. package/src/css/optional/floating-field.css +87 -0
  156. package/src/css/optional/index.css +5 -0
  157. package/src/css/optional/layout-extra.css +7 -4
  158. package/src/css/optional/otp.css +115 -0
  159. package/src/css/optional/scroll-snap.css +6 -0
  160. package/src/css/optional/scroller.css +19 -7
  161. package/src/css/optional/typography-fluid.css +5 -0
  162. package/src/css/optional/utilities-extra.css +17 -2
  163. package/src/css/prose.css +18 -8
  164. package/src/css/reset.css +0 -10
  165. package/src/css/theme.css +29 -13
  166. package/src/css/themes/cyberpunk.css +1 -1
  167. package/src/css/themes/dim.css +3 -3
  168. package/src/css/themes/edge.css +11 -37
  169. package/src/css/themes/index.css +3 -3
  170. package/src/css/themes/{dark.css → indigo.css} +5 -4
  171. package/src/css/themes/neon.css +5 -5
  172. package/src/css/themes/square.css +0 -3
  173. package/src/css/tokens.css +81 -21
  174. package/src/css/utilities.css +8 -10
  175. package/src/css/variants.css +37 -65
  176. package/src/js/command.js +1 -3
  177. package/src/js/context-menu.js +72 -53
  178. package/src/js/dialog.js +68 -79
  179. package/src/js/dismiss.js +15 -0
  180. package/src/js/enhance.js +76 -19
  181. package/src/js/enhancement-loader.js +301 -0
  182. package/src/js/events.js +3 -0
  183. package/src/js/filter.js +3 -3
  184. package/src/js/floating.js +101 -131
  185. package/src/js/flyout.js +106 -96
  186. package/src/js/focus-group.js +116 -0
  187. package/src/js/focus.js +6 -1
  188. package/src/js/index.js +4 -2
  189. package/src/js/input.js +1 -1
  190. package/src/js/keys.js +27 -0
  191. package/src/js/mask.js +7 -6
  192. package/src/js/menu.js +89 -35
  193. package/src/js/parse-config.js +47 -0
  194. package/src/js/password.js +9 -1
  195. package/src/js/scrollspy.js +133 -62
  196. package/src/js/selectors.js +14 -31
  197. package/src/js/status.js +16 -14
  198. package/src/js/surface.js +219 -125
  199. package/src/js/tab.js +46 -66
  200. package/src/js/tooltip.js +53 -31
  201. package/src/js/validation.js +45 -51
  202. package/dist/actual.js.map +0 -31
  203. package/docs/README.md +0 -26
  204. package/docs/components.md +0 -860
  205. package/docs/design-notes/previous-work.md +0 -5
  206. package/docs/forms.md +0 -1265
  207. package/docs/javascript.md +0 -426
  208. package/docs/layout.md +0 -480
  209. package/docs/patterns.md +0 -415
  210. package/docs/reserved.md +0 -7
  211. package/docs/tokens.md +0 -455
  212. package/docs/ui.md +0 -937
  213. package/docs/utilities.md +0 -384
  214. package/src/css/dialog-fallback.css +0 -56
  215. package/src/js/dialog-fallback.js +0 -163
@@ -1,22 +1,39 @@
1
1
  /*
2
2
  * Scrollspy — navigation that highlights the active section on scroll.
3
3
  *
4
- * Nav: .scrollspy containing <a href="#section-id"> links
4
+ * Nav: [data-enhance="scrollspy"] containing <a href="#section-id"> links
5
5
  * Target: elements referenced by the href fragments
6
6
  *
7
- * Uses IntersectionObserver. Falls back gracefully: links still work
8
- * even if IntersectionObserver isn't available.
7
+ * Deterministic geometry, not IntersectionObserver: on each scroll (throttled
8
+ * with requestAnimationFrame) the active section is the last one whose top has
9
+ * crossed an activation line. An IntersectionObserver reports *which* sections
10
+ * intersect, not which one is current, so it has to guess whenever several
11
+ * share the viewport — and guesses wrong for sections shorter than the band.
9
12
  *
10
- * Optional root: data-scrollspy-root="#scroll-container" observes section
11
- * visibility within a scroll container instead of the viewport.
13
+ * activation line = root top + offset (default 20% of the root's height)
14
+ * active section = last section whose top <= activation line
15
+ * scrolled to end = last section, whatever the line says
16
+ * above the first = no active section
12
17
  *
13
- * Self-registers via enhance: injected .scrollspy navs wire automatically.
14
- * Cleanup disconnects the IntersectionObserver and the small MutationObserver
15
- * that refreshes the link→section map as nav links or sections are injected.
18
+ * Optional root: data-scrollspy-root="#scroll-container" measures against a
19
+ * scroll container instead of the viewport.
20
+ * Optional offset: data-scrollspy-offset="24" | "24px" | "20%" moves the
21
+ * activation line. Invalid values fall back to 20%; negatives clamp to 0.
22
+ *
23
+ * Self-registers via registerEnhancement: injected navs wire automatically.
24
+ * Cleanup removes the scroll/resize listeners and the small MutationObserver
25
+ * that refreshes the link->section map as nav links are injected. New sections
26
+ * outside the nav subtree are not observed — call refreshScrollspy(nav) after
27
+ * those mutations.
28
+ *
29
+ * The .scrollspy class alone still gives :target-current behavior via CSS —
30
+ * a documented no-JS mode, not a fallback remark.
16
31
  */
17
32
 
18
- import enhance from "./enhance.js";
19
- import { CLASSES } from "./selectors.js";
33
+ import { registerEnhancement } from "./enhance.js";
34
+
35
+ const DEFAULT_OFFSET_RATIO = 0.2;
36
+ const navState = new WeakMap();
20
37
 
21
38
  function rootFor(nav) {
22
39
  const selector = nav.getAttribute("data-scrollspy-root");
@@ -42,80 +59,134 @@ function sectionsFor(nav) {
42
59
  return sections;
43
60
  }
44
61
 
45
- const navState = new WeakMap();
62
+ /*
63
+ * Viewport-relative measurements for either scroll root. The window branch
64
+ * reads documentElement rather than a captured window so a nav in another
65
+ * document measures its own root — same reason surface.js resolves
66
+ * ownerDocument instead of binding at import time.
67
+ */
68
+ function measureRoot(nav, root) {
69
+ if (root) {
70
+ return {
71
+ top: root.getBoundingClientRect().top,
72
+ height: root.clientHeight,
73
+ scrollTop: root.scrollTop,
74
+ scrollHeight: root.scrollHeight,
75
+ };
76
+ }
46
77
 
47
- export function refreshScrollspy(nav) {
48
- navState.get(nav)?.rebuild();
78
+ // scrollingElement is the real scroll container in standards mode; fall
79
+ // back to documentElement (quirks mode, or older engines without it).
80
+ const doc = nav.ownerDocument;
81
+ const el = doc.scrollingElement || doc.documentElement;
82
+ return {
83
+ top: 0,
84
+ height: el.clientHeight,
85
+ scrollTop: el.scrollTop,
86
+ scrollHeight: el.scrollHeight,
87
+ };
88
+ }
89
+
90
+ /*
91
+ * "24" and "24px" are pixels, "20%" is a share of the root's visible height.
92
+ * Percentages resolve per measurement, so a resize needs no rebuild.
93
+ */
94
+ function activationOffset(nav, height) {
95
+ const raw = (nav.getAttribute("data-scrollspy-offset") ?? "").trim();
96
+ if (!raw) return height * DEFAULT_OFFSET_RATIO;
97
+
98
+ const value = Number.parseFloat(raw);
99
+ if (!Number.isFinite(value)) return height * DEFAULT_OFFSET_RATIO;
100
+
101
+ return Math.max(0, raw.endsWith("%") ? (height * value) / 100 : value);
49
102
  }
50
103
 
51
104
  function setupNav(nav) {
52
- if (typeof IntersectionObserver === "undefined") return;
53
- let io = null;
105
+ const controller = new AbortController();
54
106
  let sections = [];
55
- const activeEntries = new Map();
56
- let scheduled = false;
107
+ let frame = 0;
108
+ let current;
109
+
110
+ function activate(section) {
111
+ if (section === current) return;
112
+ current = section;
113
+
114
+ for (const entry of sections) {
115
+ if (entry.section === section) {
116
+ entry.link.setAttribute("aria-current", "location");
117
+ } else {
118
+ entry.link.removeAttribute("aria-current");
119
+ }
120
+ }
121
+ }
57
122
 
58
- function rebuild() {
59
- scheduled = false;
60
- io?.disconnect();
61
- sections = sectionsFor(nav);
62
- activeEntries.clear();
123
+ function measure() {
124
+ frame = 0;
63
125
  if (!sections.length) return;
64
126
 
65
- io = new IntersectionObserver(
66
- (entries) => {
67
- for (const entry of entries) {
68
- if (entry.isIntersecting) {
69
- activeEntries.set(entry.target, entry);
70
- } else {
71
- activeEntries.delete(entry.target);
72
- }
73
- }
74
-
75
- const visible = [...activeEntries.values()].sort(
76
- (a, b) => a.boundingClientRect.top - b.boundingClientRect.top,
77
- );
78
-
79
- const id = visible[0]?.target.id;
80
- for (const { link } of sections) {
81
- if (id && link.getAttribute("href") === `#${id}`) {
82
- link.setAttribute("aria-current", "location");
83
- } else {
84
- link.removeAttribute("aria-current");
85
- }
86
- }
87
- },
88
- {
89
- root: rootFor(nav),
90
- rootMargin: "-20% 0px -70% 0px",
91
- threshold: 0,
92
- },
93
- );
127
+ const root = rootFor(nav);
128
+ const { top, height, scrollTop, scrollHeight } = measureRoot(nav, root);
129
+
130
+ // Scrolled to the end: the last section wins even when its top never
131
+ // crosses the line. This is the case IntersectionObserver cannot express.
132
+ if (scrollHeight > height && scrollTop + height >= scrollHeight - 1) {
133
+ activate(sections.at(-1).section);
134
+ return;
135
+ }
94
136
 
137
+ const line = top + activationOffset(nav, height);
138
+ let active = null;
95
139
  for (const { section } of sections) {
96
- io.observe(section);
140
+ if (section.getBoundingClientRect().top <= line) active = section;
97
141
  }
142
+
143
+ activate(active);
98
144
  }
99
145
 
100
- function scheduleRebuild() {
101
- if (scheduled) return;
102
- scheduled = true;
103
- queueMicrotask(rebuild);
146
+ function schedule(run = measure) {
147
+ if (frame) cancelAnimationFrame(frame);
148
+ frame = requestAnimationFrame(run);
104
149
  }
105
150
 
106
- const mo = new MutationObserver(scheduleRebuild);
151
+ function rebuild() {
152
+ sections = sectionsFor(nav);
153
+ current = undefined;
154
+ measure();
155
+ }
156
+
157
+ // The scroll root (custom container or the document viewport) is resolved
158
+ // once at connect and is stable for the lifetime of the connection; measure
159
+ // still re-reads the current root element so a replaced *element* under a
160
+ // stable container is handled, but swapping the container itself is out of
161
+ // contract (re-inject the nav or call refreshScrollspy after such a move).
162
+ const scrollTarget = rootFor(nav) ?? nav.ownerDocument.defaultView;
163
+ scrollTarget?.addEventListener("scroll", () => schedule(), {
164
+ passive: true,
165
+ signal: controller.signal,
166
+ });
167
+ nav.ownerDocument.defaultView?.addEventListener("resize", () => schedule(), {
168
+ passive: true,
169
+ signal: controller.signal,
170
+ });
171
+
172
+ // Observer is scoped to the nav subtree — link injection is caught;
173
+ // section-only DOM mutations elsewhere need an explicit refreshScrollspy().
174
+ const mo = new MutationObserver(() => schedule(rebuild));
107
175
  mo.observe(nav, { childList: true, subtree: true });
108
- navState.set(nav, { rebuild: scheduleRebuild });
176
+ navState.set(nav, { rebuild });
109
177
 
110
178
  rebuild();
111
179
 
112
180
  return () => {
113
- io?.disconnect();
181
+ controller.abort();
182
+ if (frame) cancelAnimationFrame(frame);
114
183
  mo.disconnect();
115
184
  navState.delete(nav);
116
185
  };
117
186
  }
118
187
 
119
- enhance({
120
- [`.${CLASSES.scrollspy}`]: setupNav,
121
- });
188
+ export function refreshScrollspy(nav) {
189
+ navState.get(nav)?.rebuild();
190
+ }
191
+
192
+ registerEnhancement("scrollspy", setupNav);
@@ -1,35 +1,29 @@
1
1
  /*
2
- * Selectors — the CSS class names this runtime looks for or writes, gathered
3
- * in one file.
2
+ * Selectors — the state-class vocabulary the runtime writes.
4
3
  *
5
- * Every module that matches against or toggles one of Actual's own class
6
- * names imports its token from here instead of writing the string inline.
7
- * To run this runtime against a different CSS framework, edit this file (or
8
- * alias it in a bundler) — no other module needs to change.
4
+ * This file no longer lists discovery selectors: behavior discovery moved
5
+ * to data-enhance tokens in 0.2. What remains are the class names modules
6
+ * write as state (is-open, was-validated, …) and validation's two read-side
7
+ * presentation adapters (field, fieldError). To run this runtime against a
8
+ * different CSS framework, edit this file (or alias it in a bundler).
9
9
  *
10
10
  * data-* attributes and ARIA are not listed: they are each module's own
11
11
  * behavior API, not CSS vocabulary, so they stay framework-neutral by
12
- * construction and never need remapping. A module with no entry here (see
13
- * status.js) has no CSS class dependency at all.
12
+ * construction and never need remapping.
14
13
  */
14
+
15
15
  export const CLASSES = {
16
- // flyout.js, context-menu.js — opt-in class on the panel/menu element.
17
- flyout: "flyout",
18
- // tab.js — opt-in class on the tablist element.
19
- tabs: "tabs",
20
- // scrollspy.js — opt-in class on the nav element.
21
- scrollspy: "scrollspy",
22
- // validation.js — opt-in class on the <form>.
23
- needsValidation: "needs-validation",
24
16
  // validation.js — written on <form> after the first submit attempt.
25
17
  wasValidated: "was-validated",
26
- // validation.js — read on the element an aria-describedby id resolves to.
27
- fieldError: "field-error",
28
- // validation.js — ancestor container searched for aria-invalid/danger state.
29
- field: "field",
30
18
  // validation.js — intents.css vocabulary class toggled on the field
31
19
  // container while any control inside it is invalid.
32
20
  danger: "danger",
21
+ // validation.js — read-side presentation adapter: error slot among
22
+ // aria-describedby targets. [data-field-error] is an accepted alias (D6).
23
+ fieldError: "field-error",
24
+ // validation.js — read-side presentation adapter: ancestor container
25
+ // for aria-invalid/danger state. Optional — degrades to no-op.
26
+ field: "field",
33
27
  // surface.js — state class on the open flyout/menu/dialog surface.
34
28
  open: "is-open",
35
29
  // surface.js — state class while the surface renders as a mobile sheet.
@@ -38,13 +32,6 @@ export const CLASSES = {
38
32
  backdrop: "surface-backdrop",
39
33
  // dialog.js — transient class for the "can't dismiss" shake animation.
40
34
  static: "is-static",
41
- // dialog-fallback.js — marker written on a dialog controlled by the legacy
42
- // shim (browsers without native <dialog>); dialog-fallback.css keys its
43
- // emulated dialog presentation on it.
44
- dialogFallback: "dialog-fallback",
45
- // dialog-fallback.js — state class on a shimmed dialog while it is open as
46
- // a modal; carries the fixed positioning and simulated backdrop.
47
- fallbackModal: "is-fallback-modal",
48
35
  // dialog.js — written on <html> while any modal dialog is open.
49
36
  modalOpen: "has-modal-open",
50
37
  // dialog.js — written on <html> when the viewport had a classic scrollbar
@@ -52,8 +39,4 @@ export const CLASSES = {
52
39
  hadScrollbar: "had-scrollbar",
53
40
  // tooltip.js — class name written on a shorthand-generated tooltip element.
54
41
  tooltip: "tooltip",
55
- // status.js — required alongside [data-status]. "status" is a common
56
- // domain word (order status, task status, …); the class keeps a
57
- // document-wide querySelector from matching an unrelated app element.
58
- statusBar: "status-bar",
59
42
  };
package/src/js/status.js CHANGED
@@ -24,23 +24,23 @@
24
24
  * - Any other code, without importing this module: dispatch `actual:status`
25
25
  * directly — `detail: { message, intent, duration }`, or `{}` to clear.
26
26
  *
27
- * The element contract is `.status-bar[data-status]`. Both are required:
28
- * "status" is a generic domain word, so the class keeps the document-wide
29
- * lookup from matching an unrelated app element that happens to carry its own
30
- * `data-status`. `intent` is applied verbatim as one or more classes on the
31
- * target (space-separated for more than one, e.g. "danger uppercase").
32
- * status.js does not know or validate any specific names — .danger, .success,
33
- * and friends are Actual's own intents.css vocabulary, not something this
34
- * module hardcodes. Each call removes the intent classes the previous call
35
- * added (tracked in data-status-classes on the target itself), then adds the
36
- * current call's, so stale intent classes never accumulate and classes the
37
- * app added independently are left alone.
27
+ * The element contract is `[data-status][role="status"]`. Both are required:
28
+ * "status" is a generic domain word, so `role="status"` keeps the
29
+ * document-wide lookup from matching an unrelated app element that happens to
30
+ * carry its own `data-status`. `intent` is applied verbatim as one or more
31
+ * classes on the target (space-separated for more than one, e.g. "danger
32
+ * uppercase"). status.js does not know or validate any specific names —
33
+ * .danger, .success, and friends are Actual's own intents.css vocabulary, not
34
+ * something this module hardcodes. Each call removes the intent classes the
35
+ * previous call added (tracked in data-status-classes on the target itself),
36
+ * then adds the current call's, so stale intent classes never accumulate and
37
+ * classes the app added independently are left alone.
38
38
  */
39
+
39
40
  import { registerCommands, targetFor } from "./command.js";
40
41
  import { EVENTS } from "./events.js";
41
- import { CLASSES } from "./selectors.js";
42
42
 
43
- const STATUS_SELECTOR = `.${CLASSES.statusBar}[data-status]`;
43
+ const STATUS_SELECTOR = '[data-status][role="status"]';
44
44
  const STATUS_CLASSES_ATTR = "statusClasses";
45
45
 
46
46
  let statusTimer;
@@ -125,7 +125,9 @@ if (typeof document !== "undefined") {
125
125
  // importing this module. A message shows it; omitting one clears it.
126
126
  document.addEventListener(EVENTS.status, (event) => {
127
127
  const { message, intent, duration } = event.detail ?? {};
128
- if (message) {
128
+ // `message != null` keeps "" (an intentional empty message) on the show
129
+ // path, consistent with status() itself; an absent message clears.
130
+ if (message != null) {
129
131
  status(message, { intent, duration });
130
132
  } else {
131
133
  status.clear();