actual-css 0.14.0 → 0.15.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 (61) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +8 -8
  3. package/components.json +51 -6
  4. package/dist/actual.full.css +367 -31
  5. package/dist/actual.full.js +1 -1
  6. package/dist/actual.full.min.css +1 -1
  7. package/docs/design-notes/widget-primitives.md +4 -0
  8. package/docs/navigation.json +4 -1
  9. package/docs/pages/components/accordion.md +15 -18
  10. package/docs/pages/components/avatar.md +1 -0
  11. package/docs/pages/components/badge.md +41 -5
  12. package/docs/pages/components/button.md +5 -0
  13. package/docs/pages/components/card.md +34 -0
  14. package/docs/pages/components/diagrams.md +96 -0
  15. package/docs/pages/components/steps.md +1 -1
  16. package/docs/pages/components/table.md +4 -0
  17. package/docs/pages/components/timeline.md +111 -0
  18. package/docs/pages/enhancements/flyout.md +4 -3
  19. package/docs/pages/enhancements/overview.md +2 -1
  20. package/docs/pages/enhancements/tabs.md +48 -6
  21. package/docs/pages/enhancements/tooltip.md +3 -1
  22. package/docs/pages/examples/overview.md +5 -2
  23. package/docs/pages/forms/choice-cards.md +3 -0
  24. package/docs/pages/forms/combobox.md +34 -19
  25. package/docs/pages/forms/controls.md +2 -0
  26. package/docs/pages/forms/date-picker.md +34 -13
  27. package/docs/pages/forms/file-upload.md +120 -0
  28. package/docs/pages/forms/overview.md +8 -0
  29. package/docs/pages/foundations/philosophy.md +6 -0
  30. package/docs/pages/foundations/theming.md +57 -5
  31. package/docs/pages/foundations/typography.md +3 -0
  32. package/docs/pages/guides/introduction.md +1 -1
  33. package/docs/pages/guides/modular-import.md +124 -0
  34. package/docs/pages/guides/upgrading.md +63 -0
  35. package/package.json +4 -3
  36. package/reserved-classes.json +3 -0
  37. package/src/css/components/accordion.css +7 -3
  38. package/src/css/components/alert.css +9 -1
  39. package/src/css/components/badge.css +38 -11
  40. package/src/css/components/button.css +10 -2
  41. package/src/css/components/card.css +9 -0
  42. package/src/css/components/index.css +1 -0
  43. package/src/css/components/skeleton.css +11 -0
  44. package/src/css/components/tab.css +77 -0
  45. package/src/css/components/table.css +7 -1
  46. package/src/css/components/timeline.css +160 -0
  47. package/src/css/core/base.css +3 -1
  48. package/src/css/core/theme.css +6 -1
  49. package/src/css/core/tokens.css +3 -3
  50. package/src/css/forms/choice-card.css +5 -2
  51. package/src/css/forms/color-swatch.css +4 -0
  52. package/src/css/forms/control.css +12 -3
  53. package/src/css/forms/custom-select.css +1 -1
  54. package/src/css/typography/prose.css +4 -2
  55. package/src/js/enhance.js +14 -0
  56. package/src/js/floating-space.js +26 -0
  57. package/src/js/flyout.js +3 -1
  58. package/src/js/parse-config.js +5 -2
  59. package/src/js/tooltip.js +2 -2
  60. package/src/tooling/design-tokens.js +2 -0
  61. package/src/tooling/theme-contrast.js +1 -1
@@ -38,7 +38,7 @@
38
38
  --control-bg: var(--surface-raised);
39
39
  outline: var(--focus-ring-width) solid var(--form-invalid-border, var(--focus));
40
40
  outline-offset: calc(var(--focus-ring-width) * -1);
41
- box-shadow: none;
41
+ box-shadow: var(--control-shadow, none);
42
42
  }
43
43
 
44
44
  &::picker-icon {
@@ -88,7 +88,7 @@
88
88
  /* --link is a theme-level token (core/base.css also reads it for bare links);
89
89
  prose falls back to --primary so a theme that never sets it is unaffected. */
90
90
  .prose :where(a) {
91
- color: var(--link, var(--primary));
91
+ color: var(--surface-link, var(--link, var(--primary)));
92
92
  text-decoration-line: underline;
93
93
  text-decoration-thickness: 0.08em;
94
94
  text-underline-offset: 0.18em;
@@ -100,7 +100,9 @@
100
100
 
101
101
  @supports (color: color-mix(in oklch, red, white)) {
102
102
  .prose :where(a:hover, a:focus-visible) {
103
- color: color-mix(in oklab, var(--link, var(--primary)), var(--text) 15%);
103
+ /* A solid surface owns its contrast pair in every state; mixing in the
104
+ page's --text would pull its links toward the wrong background. */
105
+ color: var(--surface-link, color-mix(in oklab, var(--link, var(--primary)), var(--text) 15%));
104
106
  }
105
107
  }
106
108
 
package/src/js/enhance.js CHANGED
@@ -31,6 +31,20 @@
31
31
  const registries = new WeakMap();
32
32
  const ownedNames = new WeakMap();
33
33
 
34
+ // Each evaluation owns its registries, so a second copy (the dist bundle plus
35
+ // source modules, or two bundles) registers every behavior twice and fails
36
+ // silently — a flyout opens then closes. Every built-in imports this module,
37
+ // so the marker catches any duplicated runtime once, here.
38
+ const RUNTIME = Symbol.for("actual-css.runtime");
39
+ if (typeof document !== "undefined") {
40
+ if (globalThis[RUNTIME]) {
41
+ console.warn(
42
+ "actual-css: a second copy of the JavaScript runtime is loaded. Load the full bundle or the source modules, never both.",
43
+ );
44
+ }
45
+ globalThis[RUNTIME] = true;
46
+ }
47
+
34
48
  // Node type constants as literals, not the realm's Node global, so nodes from
35
49
  // another document/window compare correctly.
36
50
  const ELEMENT_NODE = 1;
@@ -29,6 +29,32 @@ function isViewportAnchored(anchor) {
29
29
  return false;
30
30
  }
31
31
 
32
+ // Mirrors the placements @lekoala/floating accepts; it throws on any other
33
+ // value, and from an autoUpdate callback that throw would repeat on every
34
+ // scroll. Markup is validated here instead, so a typo warns once.
35
+ const PLACEMENTS = new Set(
36
+ ["top", "bottom", "left", "right"].flatMap((side) => [side, `${side}-start`, `${side}-end`]),
37
+ );
38
+ const warnedPlacements = new WeakSet();
39
+
40
+ /**
41
+ * The author's placement attribute, or null when absent or unknown.
42
+ *
43
+ * @param {Element | null | undefined} el
44
+ * @param {string} attribute
45
+ * @returns {string | null}
46
+ */
47
+ export function readPlacement(el, attribute) {
48
+ const value = el?.getAttribute(attribute);
49
+ if (!value) return null;
50
+ if (PLACEMENTS.has(value)) return value;
51
+ if (!warnedPlacements.has(el)) {
52
+ warnedPlacements.add(el);
53
+ console.warn(`Unknown ${attribute}="${value}" ignored`, el);
54
+ }
55
+ return null;
56
+ }
57
+
32
58
  /**
33
59
  * @param {Element | null | undefined} anchor
34
60
  * @returns {{ space: "viewport" | "document", position: "fixed" | "absolute" }}
package/src/js/flyout.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { registerEnhancement } from "./enhance.js";
2
+ import { readPlacement } from "./floating-space.js";
2
3
  import { focusFirstDescendant } from "./focus.js";
3
4
  import { shouldIgnoreKey } from "./keys.js";
4
5
  import { connectMenu, focusFirstMenuItem, focusLastMenuItem } from "./menu.js";
@@ -9,7 +10,8 @@ const triggerMap = new WeakMap();
9
10
  function readFlyoutOptions(panel) {
10
11
  const opts = { autoClose: "true" };
11
12
  const ds = panel.dataset;
12
- if (ds.flyoutPlacement) opts.placement = ds.flyoutPlacement;
13
+ const placement = readPlacement(panel, "data-flyout-placement");
14
+ if (placement) opts.placement = placement;
13
15
  if (ds.flyoutDistance != null) {
14
16
  const d = Number.parseFloat(ds.flyoutDistance);
15
17
  if (Number.isFinite(d)) opts.distance = d;
@@ -11,10 +11,13 @@
11
11
  */
12
12
 
13
13
  function relax(source) {
14
+ // Double-quoted strings are matched first and copied verbatim, so text
15
+ // such as `http:` inside them is never mistaken for an unquoted key.
14
16
  return source.replace(
15
- /([a-zA-Z_$][\w$-]*)\s*:|'((?:\\'|[^'])*)'/g,
16
- (_match, key, stringContent) => {
17
+ /"(?:\\.|[^"\\])*"|([a-zA-Z_$][\w$-]*)\s*:|'((?:\\'|[^'])*)'/g,
18
+ (match, key, stringContent) => {
17
19
  if (key) return `"${key}":`;
20
+ if (stringContent === undefined) return match;
18
21
  const unescaped = stringContent.replace(/\\'/g, "'");
19
22
  const escaped = unescaped.replace(/"/g, '\\"');
20
23
  return `"${escaped}"`;
package/src/js/tooltip.js CHANGED
@@ -51,7 +51,7 @@
51
51
  import { autoUpdate, reposition } from "@lekoala/floating";
52
52
  import enhance from "./enhance.js";
53
53
  import { registerEscapeDismissal } from "./escape.js";
54
- import { positionModeFor } from "./floating-space.js";
54
+ import { positionModeFor, readPlacement } from "./floating-space.js";
55
55
  import { CLASSES } from "./selectors.js";
56
56
 
57
57
  const SHOW_DELAY_MS = 150;
@@ -144,7 +144,7 @@ export function isTooltipVisible(tip) {
144
144
  * the tip's own placement never enters into it.
145
145
  */
146
146
  function placementFor(ref) {
147
- return ref?.getAttribute("data-tooltip-placement") || "top";
147
+ return readPlacement(ref, "data-tooltip-placement") ?? "top";
148
148
  }
149
149
 
150
150
  function hasOption(trigger, name) {
@@ -44,6 +44,7 @@ export const THEME_COLORS = [
44
44
  "heading",
45
45
  "border",
46
46
  "border-control",
47
+ "control-default-bg",
47
48
  "focus",
48
49
  "hover-overlay",
49
50
  "hover-overlay-solid",
@@ -101,6 +102,7 @@ export const FOUNDATIONS = {
101
102
  listed here (tests/design-tokens.test.js), so a new token cannot silently
102
103
  stay out of the design tools. */
103
104
  export const NOT_EXPORTED = {
105
+ "internal surface context, not a palette color": ["surface-link"],
104
106
  "internal 0/1 weights selecting a control step; the steps are exported": [
105
107
  "control-step-sm",
106
108
  "control-step-md",
@@ -37,7 +37,7 @@ export const INTENTS = ["primary", "secondary", "success", "warning", "danger",
37
37
  export const SURFACES = ["surface", "surface-raised", "surface-subtle"];
38
38
 
39
39
  /* Badges state .soft explicitly: a theme may fill badges by default
40
- (bootstrap-v6), which would measure a solid pair against the soft hover.
40
+ by a local rule, which would measure a solid pair against the soft hover.
41
41
  The theme name is read from a file by a regex, so it never enters this
42
42
  markup: measureContrast() sets it through dataset. `scheme` is only ever
43
43
  "light" or "dark". */