@hadoku/task-ui-components 0.0.0-stage → 4.20.4

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 (169) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +917 -2
  3. package/dist/app-header.css +288 -0
  4. package/dist/bento.css +143 -0
  5. package/dist/button.css +227 -0
  6. package/dist/components/AppHeader.d.ts +61 -0
  7. package/dist/components/AppHeader.d.ts.map +1 -0
  8. package/dist/components/AppHeader.js +23 -0
  9. package/dist/components/AppHeader.js.map +1 -0
  10. package/dist/components/BentoCard.d.ts +23 -0
  11. package/dist/components/BentoCard.d.ts.map +1 -0
  12. package/dist/components/BentoCard.js +49 -0
  13. package/dist/components/BentoCard.js.map +1 -0
  14. package/dist/components/BentoGrid.d.ts +24 -0
  15. package/dist/components/BentoGrid.d.ts.map +1 -0
  16. package/dist/components/BentoGrid.js +22 -0
  17. package/dist/components/BentoGrid.js.map +1 -0
  18. package/dist/components/Button.d.ts +28 -0
  19. package/dist/components/Button.d.ts.map +1 -0
  20. package/dist/components/Button.js +51 -0
  21. package/dist/components/Button.js.map +1 -0
  22. package/dist/components/ConnectedSettings.d.ts +53 -0
  23. package/dist/components/ConnectedSettings.d.ts.map +1 -0
  24. package/dist/components/ConnectedSettings.js +368 -0
  25. package/dist/components/ConnectedSettings.js.map +1 -0
  26. package/dist/components/ConnectedThemePicker.d.ts +52 -0
  27. package/dist/components/ConnectedThemePicker.d.ts.map +1 -0
  28. package/dist/components/ConnectedThemePicker.js +32 -0
  29. package/dist/components/ConnectedThemePicker.js.map +1 -0
  30. package/dist/components/ContextMenu.d.ts +22 -0
  31. package/dist/components/ContextMenu.d.ts.map +1 -0
  32. package/dist/components/ContextMenu.js +23 -0
  33. package/dist/components/ContextMenu.js.map +1 -0
  34. package/dist/components/HadokuThemePicker.d.ts +27 -0
  35. package/dist/components/HadokuThemePicker.d.ts.map +1 -0
  36. package/dist/components/HadokuThemePicker.js +19 -0
  37. package/dist/components/HadokuThemePicker.js.map +1 -0
  38. package/dist/components/LoadingSkeleton.d.ts +14 -0
  39. package/dist/components/LoadingSkeleton.d.ts.map +1 -0
  40. package/dist/components/LoadingSkeleton.js +62 -0
  41. package/dist/components/LoadingSkeleton.js.map +1 -0
  42. package/dist/components/Modal.d.ts +39 -0
  43. package/dist/components/Modal.d.ts.map +1 -0
  44. package/dist/components/Modal.js +106 -0
  45. package/dist/components/Modal.js.map +1 -0
  46. package/dist/components/NumberInput.d.ts +26 -0
  47. package/dist/components/NumberInput.d.ts.map +1 -0
  48. package/dist/components/NumberInput.js +131 -0
  49. package/dist/components/NumberInput.js.map +1 -0
  50. package/dist/components/Select.d.ts +52 -0
  51. package/dist/components/Select.d.ts.map +1 -0
  52. package/dist/components/Select.js +281 -0
  53. package/dist/components/Select.js.map +1 -0
  54. package/dist/components/SettingsModal.d.ts +69 -0
  55. package/dist/components/SettingsModal.d.ts.map +1 -0
  56. package/dist/components/SettingsModal.js +141 -0
  57. package/dist/components/SettingsModal.js.map +1 -0
  58. package/dist/components/ThemeIcons.d.ts +26 -0
  59. package/dist/components/ThemeIcons.d.ts.map +1 -0
  60. package/dist/components/ThemeIcons.js +74 -0
  61. package/dist/components/ThemeIcons.js.map +1 -0
  62. package/dist/components/ThemePicker.d.ts +28 -0
  63. package/dist/components/ThemePicker.d.ts.map +1 -0
  64. package/dist/components/ThemePicker.js +101 -0
  65. package/dist/components/ThemePicker.js.map +1 -0
  66. package/dist/components/Toast.d.ts +13 -0
  67. package/dist/components/Toast.d.ts.map +1 -0
  68. package/dist/components/Toast.js +70 -0
  69. package/dist/components/Toast.js.map +1 -0
  70. package/dist/components/Toaster.d.ts +19 -0
  71. package/dist/components/Toaster.d.ts.map +1 -0
  72. package/dist/components/Toaster.js +19 -0
  73. package/dist/components/Toaster.js.map +1 -0
  74. package/dist/components/Tooltip.d.ts +49 -0
  75. package/dist/components/Tooltip.d.ts.map +1 -0
  76. package/dist/components/Tooltip.js +143 -0
  77. package/dist/components/Tooltip.js.map +1 -0
  78. package/dist/context-menu.css +11 -0
  79. package/dist/controls.css +137 -0
  80. package/dist/hooks/useReducedMotion.d.ts +7 -0
  81. package/dist/hooks/useReducedMotion.d.ts.map +1 -0
  82. package/dist/hooks/useReducedMotion.js +25 -0
  83. package/dist/hooks/useReducedMotion.js.map +1 -0
  84. package/dist/hooks/useToast.d.ts +18 -0
  85. package/dist/hooks/useToast.d.ts.map +1 -0
  86. package/dist/hooks/useToast.js +28 -0
  87. package/dist/hooks/useToast.js.map +1 -0
  88. package/dist/icon-btn.css +64 -0
  89. package/dist/index.d.ts +49 -0
  90. package/dist/index.d.ts.map +1 -0
  91. package/dist/index.js +75 -0
  92. package/dist/index.js.map +1 -0
  93. package/dist/lib/menuPlacement.d.ts +16 -0
  94. package/dist/lib/menuPlacement.d.ts.map +1 -0
  95. package/dist/lib/menuPlacement.js +19 -0
  96. package/dist/lib/menuPlacement.js.map +1 -0
  97. package/dist/lib/selectMenus.d.ts +30 -0
  98. package/dist/lib/selectMenus.d.ts.map +1 -0
  99. package/dist/lib/selectMenus.js +236 -0
  100. package/dist/lib/selectMenus.js.map +1 -0
  101. package/dist/lib/settingsClient.d.ts +144 -0
  102. package/dist/lib/settingsClient.d.ts.map +1 -0
  103. package/dist/lib/settingsClient.js +157 -0
  104. package/dist/lib/settingsClient.js.map +1 -0
  105. package/dist/lib/themeScope.d.ts +19 -0
  106. package/dist/lib/themeScope.d.ts.map +1 -0
  107. package/dist/lib/themeScope.js +10 -0
  108. package/dist/lib/themeScope.js.map +1 -0
  109. package/dist/lib/titleTooltips.d.ts +38 -0
  110. package/dist/lib/titleTooltips.d.ts.map +1 -0
  111. package/dist/lib/titleTooltips.js +166 -0
  112. package/dist/lib/titleTooltips.js.map +1 -0
  113. package/dist/lib/tooltipPlacement.d.ts +14 -0
  114. package/dist/lib/tooltipPlacement.d.ts.map +1 -0
  115. package/dist/lib/tooltipPlacement.js +34 -0
  116. package/dist/lib/tooltipPlacement.js.map +1 -0
  117. package/dist/modal.css +129 -0
  118. package/dist/number-input.css +131 -0
  119. package/dist/scrollbar.css +165 -0
  120. package/dist/select.css +251 -0
  121. package/dist/styles.css +1866 -0
  122. package/dist/task-ui-components.css +1866 -0
  123. package/dist/theme-picker.css +219 -0
  124. package/dist/toaster.css +159 -0
  125. package/dist/tooltip.css +103 -0
  126. package/dist/types/index.d.ts +37 -0
  127. package/dist/types/index.d.ts.map +1 -0
  128. package/package.json +60 -4
  129. package/src/app-header.css +288 -0
  130. package/src/bento.css +143 -0
  131. package/src/button.css +227 -0
  132. package/src/components/AppHeader.tsx +87 -0
  133. package/src/components/BentoCard.tsx +72 -0
  134. package/src/components/BentoGrid.tsx +48 -0
  135. package/src/components/Button.tsx +81 -0
  136. package/src/components/ConnectedSettings.tsx +545 -0
  137. package/src/components/ConnectedThemePicker.tsx +79 -0
  138. package/src/components/ContextMenu.tsx +48 -0
  139. package/src/components/HadokuThemePicker.tsx +39 -0
  140. package/src/components/LoadingSkeleton.tsx +84 -0
  141. package/src/components/Modal.tsx +171 -0
  142. package/src/components/NumberInput.tsx +161 -0
  143. package/src/components/Select.tsx +359 -0
  144. package/src/components/SettingsModal.tsx +235 -0
  145. package/src/components/ThemeIcons.tsx +127 -0
  146. package/src/components/ThemePicker.tsx +149 -0
  147. package/src/components/Toast.tsx +92 -0
  148. package/src/components/Toaster.tsx +43 -0
  149. package/src/components/Tooltip.tsx +212 -0
  150. package/src/context-menu.css +11 -0
  151. package/src/controls.css +137 -0
  152. package/src/hooks/useReducedMotion.ts +36 -0
  153. package/src/hooks/useToast.ts +57 -0
  154. package/src/icon-btn.css +64 -0
  155. package/src/index.ts +153 -0
  156. package/src/lib/menuPlacement.ts +31 -0
  157. package/src/lib/selectMenus.ts +303 -0
  158. package/src/lib/settingsClient.ts +425 -0
  159. package/src/lib/themeScope.ts +23 -0
  160. package/src/lib/titleTooltips.ts +236 -0
  161. package/src/lib/tooltipPlacement.ts +59 -0
  162. package/src/modal.css +129 -0
  163. package/src/number-input.css +131 -0
  164. package/src/scrollbar.css +165 -0
  165. package/src/select.css +251 -0
  166. package/src/theme-picker.css +219 -0
  167. package/src/toaster.css +159 -0
  168. package/src/tooltip.css +103 -0
  169. package/src/types/index.ts +43 -0
@@ -0,0 +1,10 @@
1
+ function themeScopeOf(anchor) {
2
+ if (!anchor) return null;
3
+ const scope = anchor.closest("[data-theme]");
4
+ if (!scope || scope === document.documentElement) return document.body;
5
+ return scope;
6
+ }
7
+ export {
8
+ themeScopeOf
9
+ };
10
+ //# sourceMappingURL=themeScope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"themeScope.js","sources":["../../src/lib/themeScope.ts"],"sourcesContent":["/**\n * Where a popup (tooltip bubble, dropdown list) should be portalled.\n *\n * Theme tokens are scoped: HadokuThemeRoot puts `data-theme` on the app\n * container, and a nested `[data-theme]` scope (a theme preview, an embedded\n * board) can sit inside that. A popup must render inside the SAME scope as its\n * anchor, or it paints in the wrong theme — portalled to <body>, it loses the\n * container's tokens entirely.\n *\n * Rendering it in place keeps the theme but breaks on any ancestor with a\n * `transform`, which becomes the containing block for `position: fixed` and\n * drags the popup off its anchor. The nearest `[data-theme]` ancestor is the\n * best of both: the right tokens, and above every transform between the\n * anchor and the theme root. When the only match is <html>, <body> takes it —\n * :root tokens reach body, and a portal into <html> itself would sit outside\n * the document's body.\n */\nexport function themeScopeOf(anchor: Element | null): HTMLElement | null {\n if (!anchor) return null\n const scope = anchor.closest<HTMLElement>('[data-theme]')\n if (!scope || scope === document.documentElement) return document.body\n return scope\n}\n"],"names":[],"mappings":"AAiBO,SAAS,aAAa,QAA4C;AACvE,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,QAAQ,OAAO,QAAqB,cAAc;AACxD,MAAI,CAAC,SAAS,UAAU,SAAS,wBAAwB,SAAS;AAClE,SAAO;AACT;"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Card tooltips for every `title=` on a page, with no change to the markup.
3
+ *
4
+ * import { installTitleTooltips } from '@wolffm/task-ui-components/title-tooltips'
5
+ * import '@wolffm/task-ui-components/tooltip.css'
6
+ * installTitleTooltips()
7
+ *
8
+ * hadoku_site calls this once from its shell, which covers every
9
+ * micro-frontend at once; an app does not call it itself. Framework-free and
10
+ * React-free on purpose: the shell has no React, and this must not pull a
11
+ * second copy into the page.
12
+ *
13
+ * One delegated listener set on the document. On hover (mouse only — touch
14
+ * never showed a native title either) or keyboard focus, the nearest element
15
+ * carrying `title` lends it to the bubble: the attribute moves to
16
+ * `data-hdk-title` for as long as the bubble is up, which is what stops the
17
+ * browser drawing its own on top, and goes back the moment it closes. So
18
+ * outside a hover the DOM is exactly what the app rendered, and code or a
19
+ * framework that reads or re-sets `title` sees its own value. A title the
20
+ * app changes mid-hover (a MutationObserver watches the anchor) re-stashes and
21
+ * updates the open bubble.
22
+ *
23
+ * Same contract as <Tooltip>: hover or focus shows it, Escape dismisses it
24
+ * until the pointer leaves, the pointer can move onto the bubble, a press
25
+ * closes it, and the anchor gets `aria-describedby` while it is up (skipped
26
+ * when the title only repeats the aria-label). The bubble is portalled into
27
+ * the anchor's theme scope, so a nested `[data-theme]` gets its own colours.
28
+ *
29
+ * Opt out per subtree with `data-native-title`.
30
+ */
31
+ import { type TooltipPlacement } from './tooltipPlacement';
32
+ export interface TitleTooltipOptions {
33
+ /** Hover delay before showing, ms. Focus shows immediately. */
34
+ delay?: number;
35
+ placement?: TooltipPlacement;
36
+ }
37
+ export declare function installTitleTooltips(options?: TitleTooltipOptions): () => void;
38
+ //# sourceMappingURL=titleTooltips.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"titleTooltips.d.ts","sourceRoot":"","sources":["../../src/lib/titleTooltips.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAGH,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAExE,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,gBAAgB,CAAA;CAC7B;AAQD,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,mBAAwB,GAAG,MAAM,IAAI,CA6LlF"}
@@ -0,0 +1,166 @@
1
+ import { themeScopeOf } from "./themeScope.js";
2
+ import { placeTooltip } from "./tooltipPlacement.js";
3
+ const HIDE_GRACE_MS = 120;
4
+ const STASH = "data-hdk-title";
5
+ const BUBBLE_ID = "hdk-title-tooltip";
6
+ let uninstallCurrent = null;
7
+ function installTitleTooltips(options = {}) {
8
+ if (uninstallCurrent) return uninstallCurrent;
9
+ const delay = options.delay ?? 300;
10
+ const placement = options.placement ?? "top";
11
+ let anchor = null;
12
+ let bubble = null;
13
+ let timer;
14
+ let dismissed = null;
15
+ const observer = new MutationObserver(() => {
16
+ if (!anchor || !anchor.hasAttribute("title")) return;
17
+ stash(anchor);
18
+ if (bubble) {
19
+ bubble.textContent = anchor.getAttribute(STASH);
20
+ place();
21
+ }
22
+ });
23
+ function stash(el) {
24
+ el.setAttribute(STASH, el.getAttribute("title") ?? "");
25
+ el.removeAttribute("title");
26
+ }
27
+ function restore(el) {
28
+ const text = el.getAttribute(STASH);
29
+ if (text === null) return;
30
+ el.removeAttribute(STASH);
31
+ if (!el.hasAttribute("title")) el.setAttribute("title", text);
32
+ }
33
+ function titledFrom(target) {
34
+ if (!(target instanceof Element)) return null;
35
+ const el = target.closest(`[title], [${STASH}]`);
36
+ if (!el || el.tagName === "IFRAME" || el.closest("[data-native-title]")) return null;
37
+ return (el.getAttribute("title") ?? el.getAttribute(STASH) ?? "").trim() ? el : null;
38
+ }
39
+ function place() {
40
+ if (!anchor || !bubble) return;
41
+ if (!anchor.isConnected) {
42
+ hide(0);
43
+ return;
44
+ }
45
+ const p = placeTooltip(
46
+ placement,
47
+ anchor.getBoundingClientRect(),
48
+ bubble.offsetWidth,
49
+ bubble.offsetHeight
50
+ );
51
+ bubble.style.left = `${p.left}px`;
52
+ bubble.style.top = `${p.top}px`;
53
+ bubble.style.setProperty("--hdk-tooltip-arrow", `${p.arrow}px`);
54
+ bubble.className = `hdk-tooltip hdk-tooltip--card hdk-tooltip--plain hdk-tooltip--${p.side} hdk-tooltip--visible`;
55
+ }
56
+ function arm(el, after) {
57
+ clearTimeout(timer);
58
+ if (el !== anchor) {
59
+ release();
60
+ anchor = el;
61
+ stash(el);
62
+ observer.observe(el, { attributes: true, attributeFilter: ["title"] });
63
+ }
64
+ if (dismissed === el) return;
65
+ timer = setTimeout(open, after);
66
+ }
67
+ function open() {
68
+ if (!anchor) return;
69
+ const scope = themeScopeOf(anchor);
70
+ if (!scope) return;
71
+ if (!bubble) {
72
+ bubble = document.createElement("div");
73
+ bubble.id = BUBBLE_ID;
74
+ bubble.setAttribute("role", "tooltip");
75
+ bubble.addEventListener("pointerenter", () => clearTimeout(timer));
76
+ bubble.addEventListener("pointerleave", () => hide(HIDE_GRACE_MS));
77
+ }
78
+ const text = anchor.getAttribute(STASH) ?? "";
79
+ bubble.textContent = text;
80
+ bubble.className = `hdk-tooltip hdk-tooltip--card hdk-tooltip--plain hdk-tooltip--${placement}`;
81
+ bubble.style.left = "0px";
82
+ bubble.style.top = "0px";
83
+ if (bubble.parentNode !== scope) scope.appendChild(bubble);
84
+ if (text !== anchor.getAttribute("aria-label")) {
85
+ const ids = (anchor.getAttribute("aria-describedby") ?? "").split(/\s+/).filter(Boolean);
86
+ if (!ids.includes(BUBBLE_ID))
87
+ anchor.setAttribute("aria-describedby", [...ids, BUBBLE_ID].join(" "));
88
+ }
89
+ place();
90
+ window.addEventListener("scroll", place, true);
91
+ window.addEventListener("resize", place);
92
+ }
93
+ function release() {
94
+ window.removeEventListener("scroll", place, true);
95
+ window.removeEventListener("resize", place);
96
+ bubble?.remove();
97
+ if (!anchor) return;
98
+ observer.disconnect();
99
+ const ids = (anchor.getAttribute("aria-describedby") ?? "").split(/\s+/).filter((id) => id && id !== BUBBLE_ID);
100
+ if (ids.length) anchor.setAttribute("aria-describedby", ids.join(" "));
101
+ else anchor.removeAttribute("aria-describedby");
102
+ restore(anchor);
103
+ anchor = null;
104
+ }
105
+ function hide(after) {
106
+ clearTimeout(timer);
107
+ if (after === 0) release();
108
+ else timer = setTimeout(release, after);
109
+ }
110
+ const onPointerOver = (e) => {
111
+ if (e.pointerType !== "mouse") return;
112
+ if (bubble && bubble.contains(e.target)) return;
113
+ const el = titledFrom(e.target);
114
+ if (el && el === anchor && bubble?.isConnected) clearTimeout(timer);
115
+ else if (el) arm(el, delay);
116
+ };
117
+ const onPointerOut = (e) => {
118
+ if (e.pointerType !== "mouse") return;
119
+ const to = e.relatedTarget;
120
+ if (dismissed && !(to && dismissed.contains(to))) dismissed = null;
121
+ if (!anchor || to && (anchor.contains(to) || bubble?.contains(to))) return;
122
+ hide(HIDE_GRACE_MS);
123
+ };
124
+ const onPointerDown = () => {
125
+ if (anchor) dismissed = anchor;
126
+ hide(0);
127
+ };
128
+ const onFocusIn = (e) => {
129
+ const target = e.target;
130
+ if (!(target instanceof Element) || !target.matches(":focus-visible")) return;
131
+ const el = titledFrom(target);
132
+ if (el) {
133
+ dismissed = null;
134
+ arm(el, 0);
135
+ }
136
+ };
137
+ const onFocusOut = (e) => {
138
+ if (anchor && anchor.contains(e.target)) hide(0);
139
+ };
140
+ const onKeyDown = (e) => {
141
+ if (e.key !== "Escape" || !anchor || !bubble?.isConnected) return;
142
+ dismissed = anchor;
143
+ hide(0);
144
+ };
145
+ document.addEventListener("pointerover", onPointerOver);
146
+ document.addEventListener("pointerout", onPointerOut);
147
+ document.addEventListener("pointerdown", onPointerDown, true);
148
+ document.addEventListener("focusin", onFocusIn);
149
+ document.addEventListener("focusout", onFocusOut);
150
+ document.addEventListener("keydown", onKeyDown);
151
+ uninstallCurrent = () => {
152
+ hide(0);
153
+ document.removeEventListener("pointerover", onPointerOver);
154
+ document.removeEventListener("pointerout", onPointerOut);
155
+ document.removeEventListener("pointerdown", onPointerDown, true);
156
+ document.removeEventListener("focusin", onFocusIn);
157
+ document.removeEventListener("focusout", onFocusOut);
158
+ document.removeEventListener("keydown", onKeyDown);
159
+ uninstallCurrent = null;
160
+ };
161
+ return uninstallCurrent;
162
+ }
163
+ export {
164
+ installTitleTooltips
165
+ };
166
+ //# sourceMappingURL=titleTooltips.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"titleTooltips.js","sources":["../../src/lib/titleTooltips.ts"],"sourcesContent":["/**\n * Card tooltips for every `title=` on a page, with no change to the markup.\n *\n * import { installTitleTooltips } from '@wolffm/task-ui-components/title-tooltips'\n * import '@wolffm/task-ui-components/tooltip.css'\n * installTitleTooltips()\n *\n * hadoku_site calls this once from its shell, which covers every\n * micro-frontend at once; an app does not call it itself. Framework-free and\n * React-free on purpose: the shell has no React, and this must not pull a\n * second copy into the page.\n *\n * One delegated listener set on the document. On hover (mouse only — touch\n * never showed a native title either) or keyboard focus, the nearest element\n * carrying `title` lends it to the bubble: the attribute moves to\n * `data-hdk-title` for as long as the bubble is up, which is what stops the\n * browser drawing its own on top, and goes back the moment it closes. So\n * outside a hover the DOM is exactly what the app rendered, and code or a\n * framework that reads or re-sets `title` sees its own value. A title the\n * app changes mid-hover (a MutationObserver watches the anchor) re-stashes and\n * updates the open bubble.\n *\n * Same contract as <Tooltip>: hover or focus shows it, Escape dismisses it\n * until the pointer leaves, the pointer can move onto the bubble, a press\n * closes it, and the anchor gets `aria-describedby` while it is up (skipped\n * when the title only repeats the aria-label). The bubble is portalled into\n * the anchor's theme scope, so a nested `[data-theme]` gets its own colours.\n *\n * Opt out per subtree with `data-native-title`.\n */\n\nimport { themeScopeOf } from './themeScope'\nimport { placeTooltip, type TooltipPlacement } from './tooltipPlacement'\n\nexport interface TitleTooltipOptions {\n /** Hover delay before showing, ms. Focus shows immediately. */\n delay?: number\n placement?: TooltipPlacement\n}\n\nconst HIDE_GRACE_MS = 120\nconst STASH = 'data-hdk-title'\nconst BUBBLE_ID = 'hdk-title-tooltip'\n\nlet uninstallCurrent: (() => void) | null = null\n\nexport function installTitleTooltips(options: TitleTooltipOptions = {}): () => void {\n if (uninstallCurrent) return uninstallCurrent\n const delay = options.delay ?? 300\n const placement = options.placement ?? 'top'\n\n let anchor: Element | null = null\n let bubble: HTMLDivElement | null = null\n let timer: ReturnType<typeof setTimeout> | undefined\n // Escape closes the bubble until the pointer leaves that anchor (WCAG 1.4.13).\n let dismissed: Element | null = null\n\n const observer = new MutationObserver(() => {\n if (!anchor || !anchor.hasAttribute('title')) return\n stash(anchor)\n if (bubble) {\n bubble.textContent = anchor.getAttribute(STASH)\n place()\n }\n })\n\n function stash(el: Element) {\n el.setAttribute(STASH, el.getAttribute('title') ?? '')\n el.removeAttribute('title')\n }\n\n function restore(el: Element) {\n const text = el.getAttribute(STASH)\n if (text === null) return\n el.removeAttribute(STASH)\n if (!el.hasAttribute('title')) el.setAttribute('title', text)\n }\n\n function titledFrom(target: EventTarget | null): Element | null {\n if (!(target instanceof Element)) return null\n // The stash too: while the bubble is up the anchor carries data-hdk-title,\n // not title, and a pointerover on its own icon must still find IT rather\n // than a titled ancestor.\n const el = target.closest(`[title], [${STASH}]`)\n if (!el || el.tagName === 'IFRAME' || el.closest('[data-native-title]')) return null\n return (el.getAttribute('title') ?? el.getAttribute(STASH) ?? '').trim() ? el : null\n }\n\n function place() {\n if (!anchor || !bubble) return\n if (!anchor.isConnected) {\n hide(0)\n return\n }\n const p = placeTooltip(\n placement,\n anchor.getBoundingClientRect(),\n bubble.offsetWidth,\n bubble.offsetHeight\n )\n bubble.style.left = `${p.left}px`\n bubble.style.top = `${p.top}px`\n bubble.style.setProperty('--hdk-tooltip-arrow', `${p.arrow}px`)\n bubble.className = `hdk-tooltip hdk-tooltip--card hdk-tooltip--plain hdk-tooltip--${p.side} hdk-tooltip--visible`\n }\n\n /** Take the title off `el` now (before the browser's own ~1s delay runs\n * out) and open the bubble after `after` ms. */\n function arm(el: Element, after: number) {\n clearTimeout(timer)\n if (el !== anchor) {\n release()\n anchor = el\n stash(el)\n observer.observe(el, { attributes: true, attributeFilter: ['title'] })\n }\n if (dismissed === el) return\n timer = setTimeout(open, after)\n }\n\n function open() {\n if (!anchor) return\n const scope = themeScopeOf(anchor)\n if (!scope) return\n if (!bubble) {\n bubble = document.createElement('div')\n bubble.id = BUBBLE_ID\n bubble.setAttribute('role', 'tooltip')\n bubble.addEventListener('pointerenter', () => clearTimeout(timer))\n bubble.addEventListener('pointerleave', () => hide(HIDE_GRACE_MS))\n }\n const text = anchor.getAttribute(STASH) ?? ''\n bubble.textContent = text\n // Parked invisibly at the origin so it can be measured, then placed.\n bubble.className = `hdk-tooltip hdk-tooltip--card hdk-tooltip--plain hdk-tooltip--${placement}`\n bubble.style.left = '0px'\n bubble.style.top = '0px'\n if (bubble.parentNode !== scope) scope.appendChild(bubble)\n if (text !== anchor.getAttribute('aria-label')) {\n const ids = (anchor.getAttribute('aria-describedby') ?? '').split(/\\s+/).filter(Boolean)\n if (!ids.includes(BUBBLE_ID))\n anchor.setAttribute('aria-describedby', [...ids, BUBBLE_ID].join(' '))\n }\n place()\n window.addEventListener('scroll', place, true)\n window.addEventListener('resize', place)\n }\n\n /** Close the bubble and hand the anchor its title back. */\n function release() {\n window.removeEventListener('scroll', place, true)\n window.removeEventListener('resize', place)\n bubble?.remove()\n if (!anchor) return\n observer.disconnect()\n const ids = (anchor.getAttribute('aria-describedby') ?? '')\n .split(/\\s+/)\n .filter(id => id && id !== BUBBLE_ID)\n if (ids.length) anchor.setAttribute('aria-describedby', ids.join(' '))\n else anchor.removeAttribute('aria-describedby')\n restore(anchor)\n anchor = null\n }\n\n function hide(after: number) {\n clearTimeout(timer)\n if (after === 0) release()\n else timer = setTimeout(release, after)\n }\n\n const onPointerOver = (e: PointerEvent) => {\n if (e.pointerType !== 'mouse') return\n if (bubble && bubble.contains(e.target as Node)) return\n // Back over the anchor of a bubble still up: just cancel the pending hide.\n // Over it with no bubble (inside the hide grace, or after a dismissal\n // that has since cleared), arm it again — otherwise it stays silent.\n const el = titledFrom(e.target)\n if (el && el === anchor && bubble?.isConnected) clearTimeout(timer)\n else if (el) arm(el, delay)\n }\n\n const onPointerOut = (e: PointerEvent) => {\n if (e.pointerType !== 'mouse') return\n const to = e.relatedTarget as Node | null\n // Checked before `anchor`: a dismissal has already released the anchor.\n if (dismissed && !(to && dismissed.contains(to))) dismissed = null\n if (!anchor || (to && (anchor.contains(to) || bubble?.contains(to)))) return\n hide(HIDE_GRACE_MS)\n }\n\n // A press closes it, as it does a native title: the click usually opens\n // something the bubble would sit on top of.\n const onPointerDown = () => {\n if (anchor) dismissed = anchor\n hide(0)\n }\n\n const onFocusIn = (e: FocusEvent) => {\n const target = e.target\n if (!(target instanceof Element) || !target.matches(':focus-visible')) return\n const el = titledFrom(target)\n if (el) {\n dismissed = null\n arm(el, 0)\n }\n }\n\n const onFocusOut = (e: FocusEvent) => {\n if (anchor && anchor.contains(e.target as Node)) hide(0)\n }\n\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key !== 'Escape' || !anchor || !bubble?.isConnected) return\n dismissed = anchor\n hide(0)\n }\n\n document.addEventListener('pointerover', onPointerOver)\n document.addEventListener('pointerout', onPointerOut)\n document.addEventListener('pointerdown', onPointerDown, true)\n document.addEventListener('focusin', onFocusIn)\n document.addEventListener('focusout', onFocusOut)\n document.addEventListener('keydown', onKeyDown)\n\n uninstallCurrent = () => {\n hide(0)\n document.removeEventListener('pointerover', onPointerOver)\n document.removeEventListener('pointerout', onPointerOut)\n document.removeEventListener('pointerdown', onPointerDown, true)\n document.removeEventListener('focusin', onFocusIn)\n document.removeEventListener('focusout', onFocusOut)\n document.removeEventListener('keydown', onKeyDown)\n uninstallCurrent = null\n }\n return uninstallCurrent\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Where a tooltip bubble goes, shared by <Tooltip> and installTitleTooltips so
3
+ * the React and framework-free bubbles sit, flip and clamp identically.
4
+ */
5
+ export type TooltipPlacement = 'top' | 'bottom' | 'left' | 'right';
6
+ export interface Placed {
7
+ side: TooltipPlacement;
8
+ left: number;
9
+ top: number;
10
+ /** Arrow offset along the bubble edge, so it still points at the anchor after clamping. */
11
+ arrow: number;
12
+ }
13
+ export declare function placeTooltip(preferred: TooltipPlacement, a: DOMRect, w: number, h: number): Placed;
14
+ //# sourceMappingURL=tooltipPlacement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tooltipPlacement.d.ts","sourceRoot":"","sources":["../../src/lib/tooltipPlacement.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAA;AAElE,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,gBAAgB,CAAA;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,2FAA2F;IAC3F,KAAK,EAAE,MAAM,CAAA;CACd;AA0BD,wBAAgB,YAAY,CAC1B,SAAS,EAAE,gBAAgB,EAC3B,CAAC,EAAE,OAAO,EACV,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,GACR,MAAM,CAcR"}
@@ -0,0 +1,34 @@
1
+ const GAP = 8;
2
+ const EDGE = 8;
3
+ const OPPOSITE = {
4
+ top: "bottom",
5
+ bottom: "top",
6
+ left: "right",
7
+ right: "left"
8
+ };
9
+ function fits(side, a, w, h) {
10
+ if (side === "top") return a.top - GAP - h >= EDGE;
11
+ if (side === "bottom") return a.bottom + GAP + h <= window.innerHeight - EDGE;
12
+ if (side === "left") return a.left - GAP - w >= EDGE;
13
+ return a.right + GAP + w <= window.innerWidth - EDGE;
14
+ }
15
+ function snap(v) {
16
+ const dpr = window.devicePixelRatio || 1;
17
+ return Math.round(v * dpr) / dpr;
18
+ }
19
+ function placeTooltip(preferred, a, w, h) {
20
+ const side = fits(preferred, a, w, h) || !fits(OPPOSITE[preferred], a, w, h) ? preferred : OPPOSITE[preferred];
21
+ const clamp = (v, max) => Math.min(Math.max(v, EDGE), max - EDGE);
22
+ if (side === "top" || side === "bottom") {
23
+ const left2 = snap(clamp(a.left + a.width / 2 - w / 2, window.innerWidth - w));
24
+ const top2 = snap(side === "top" ? a.top - GAP - h : a.bottom + GAP);
25
+ return { side, left: left2, top: top2, arrow: a.left + a.width / 2 - left2 };
26
+ }
27
+ const top = snap(clamp(a.top + a.height / 2 - h / 2, window.innerHeight - h));
28
+ const left = snap(side === "left" ? a.left - GAP - w : a.right + GAP);
29
+ return { side, left, top, arrow: a.top + a.height / 2 - top };
30
+ }
31
+ export {
32
+ placeTooltip
33
+ };
34
+ //# sourceMappingURL=tooltipPlacement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tooltipPlacement.js","sources":["../../src/lib/tooltipPlacement.ts"],"sourcesContent":["/**\n * Where a tooltip bubble goes, shared by <Tooltip> and installTitleTooltips so\n * the React and framework-free bubbles sit, flip and clamp identically.\n */\n\nexport type TooltipPlacement = 'top' | 'bottom' | 'left' | 'right'\n\nexport interface Placed {\n side: TooltipPlacement\n left: number\n top: number\n /** Arrow offset along the bubble edge, so it still points at the anchor after clamping. */\n arrow: number\n}\n\nconst GAP = 8\nconst EDGE = 8\n\nconst OPPOSITE: Record<TooltipPlacement, TooltipPlacement> = {\n top: 'bottom',\n bottom: 'top',\n left: 'right',\n right: 'left'\n}\n\nfunction fits(side: TooltipPlacement, a: DOMRect, w: number, h: number) {\n if (side === 'top') return a.top - GAP - h >= EDGE\n if (side === 'bottom') return a.bottom + GAP + h <= window.innerHeight - EDGE\n if (side === 'left') return a.left - GAP - w >= EDGE\n return a.right + GAP + w <= window.innerWidth - EDGE\n}\n\n/** Onto the device-pixel grid. A bubble at a fractional offset draws its 1px\n * border antialiased across two rows of the screen — a blurred edge. */\nfunction snap(v: number) {\n const dpr = window.devicePixelRatio || 1\n return Math.round(v * dpr) / dpr\n}\n\nexport function placeTooltip(\n preferred: TooltipPlacement,\n a: DOMRect,\n w: number,\n h: number\n): Placed {\n const side =\n fits(preferred, a, w, h) || !fits(OPPOSITE[preferred], a, w, h)\n ? preferred\n : OPPOSITE[preferred]\n const clamp = (v: number, max: number) => Math.min(Math.max(v, EDGE), max - EDGE)\n if (side === 'top' || side === 'bottom') {\n const left = snap(clamp(a.left + a.width / 2 - w / 2, window.innerWidth - w))\n const top = snap(side === 'top' ? a.top - GAP - h : a.bottom + GAP)\n return { side, left, top, arrow: a.left + a.width / 2 - left }\n }\n const top = snap(clamp(a.top + a.height / 2 - h / 2, window.innerHeight - h))\n const left = snap(side === 'left' ? a.left - GAP - w : a.right + GAP)\n return { side, left, top, arrow: a.top + a.height / 2 - top }\n}\n"],"names":["left","top"],"mappings":"AAeA,MAAM,MAAM;AACZ,MAAM,OAAO;AAEb,MAAM,WAAuD;AAAA,EAC3D,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,OAAO;AACT;AAEA,SAAS,KAAK,MAAwB,GAAY,GAAW,GAAW;AACtE,MAAI,SAAS,MAAO,QAAO,EAAE,MAAM,MAAM,KAAK;AAC9C,MAAI,SAAS,SAAU,QAAO,EAAE,SAAS,MAAM,KAAK,OAAO,cAAc;AACzE,MAAI,SAAS,OAAQ,QAAO,EAAE,OAAO,MAAM,KAAK;AAChD,SAAO,EAAE,QAAQ,MAAM,KAAK,OAAO,aAAa;AAClD;AAIA,SAAS,KAAK,GAAW;AACvB,QAAM,MAAM,OAAO,oBAAoB;AACvC,SAAO,KAAK,MAAM,IAAI,GAAG,IAAI;AAC/B;AAEO,SAAS,aACd,WACA,GACA,GACA,GACQ;AACR,QAAM,OACJ,KAAK,WAAW,GAAG,GAAG,CAAC,KAAK,CAAC,KAAK,SAAS,SAAS,GAAG,GAAG,GAAG,CAAC,IAC1D,YACA,SAAS,SAAS;AACxB,QAAM,QAAQ,CAAC,GAAW,QAAgB,KAAK,IAAI,KAAK,IAAI,GAAG,IAAI,GAAG,MAAM,IAAI;AAChF,MAAI,SAAS,SAAS,SAAS,UAAU;AACvC,UAAMA,QAAO,KAAK,MAAM,EAAE,OAAO,EAAE,QAAQ,IAAI,IAAI,GAAG,OAAO,aAAa,CAAC,CAAC;AAC5E,UAAMC,OAAM,KAAK,SAAS,QAAQ,EAAE,MAAM,MAAM,IAAI,EAAE,SAAS,GAAG;AAClE,WAAO,EAAE,MAAM,MAAAD,OAAM,KAAAC,MAAK,OAAO,EAAE,OAAO,EAAE,QAAQ,IAAID,MAAAA;AAAAA,EAC1D;AACA,QAAM,MAAM,KAAK,MAAM,EAAE,MAAM,EAAE,SAAS,IAAI,IAAI,GAAG,OAAO,cAAc,CAAC,CAAC;AAC5E,QAAM,OAAO,KAAK,SAAS,SAAS,EAAE,OAAO,MAAM,IAAI,EAAE,QAAQ,GAAG;AACpE,SAAO,EAAE,MAAM,MAAM,KAAK,OAAO,EAAE,MAAM,EAAE,SAAS,IAAI,IAAA;AAC1D;"}
package/dist/modal.css ADDED
@@ -0,0 +1,129 @@
1
+ /* Modal styles for @wolffm/task-ui-components.
2
+ *
3
+ * The component shipped without these, which is why it had one adopter: every
4
+ * other app hand-rolled a dialog rather than reproduce the stylesheet. Import
5
+ * alongside the other component CSS:
6
+ * import '@wolffm/task-ui-components/modal.css'
7
+ *
8
+ * Tokens only, so it themes with everything else across all 18 themes.
9
+ */
10
+
11
+ .modal-overlay {
12
+ position: fixed;
13
+ inset: 0;
14
+ background-color: var(--color-bg-overlay);
15
+ display: flex;
16
+ align-items: flex-start;
17
+ justify-content: center;
18
+ padding: 8vh 16px;
19
+ z-index: 50;
20
+ }
21
+
22
+ .modal-card {
23
+ background-color: var(--color-bg-card);
24
+ color: var(--color-text);
25
+ border: 1px solid var(--color-border);
26
+ border-radius: var(--hdk-radius-lg);
27
+ padding: 20px;
28
+ width: min(440px, 92vw);
29
+ max-height: 84vh;
30
+ overflow: auto;
31
+ box-shadow: var(--hdk-shadow-lg);
32
+ }
33
+
34
+ /* The card takes focus on open so Escape works before anything inside is
35
+ * focused; it must not draw a focus ring for that. Controls keep theirs. */
36
+ .modal-card:focus {
37
+ outline: none;
38
+ }
39
+
40
+ /* A dialog that needs room — a review step, a side-by-side. */
41
+ .modal-card--wide {
42
+ width: min(680px, 94vw);
43
+ }
44
+
45
+ .modal-head {
46
+ display: flex;
47
+ align-items: center;
48
+ gap: 10px;
49
+ margin: 0 0 14px;
50
+ }
51
+
52
+ .modal-card h3 {
53
+ margin: 0;
54
+ font-size: var(--hdk-text-lg);
55
+ font-weight: 600;
56
+ }
57
+
58
+ .modal-card label {
59
+ display: block;
60
+ color: var(--color-text-secondary);
61
+ font-size: var(--hdk-text-sm);
62
+ margin: 12px 0 5px;
63
+ }
64
+
65
+ .modal-input {
66
+ width: 100%;
67
+ font: inherit;
68
+ font-size: var(--hdk-text-sm);
69
+ color: var(--color-text);
70
+ background-color: var(--color-bg-alt);
71
+ border: 1px solid var(--color-border);
72
+ border-radius: var(--hdk-radius);
73
+ padding: 9px 11px;
74
+ }
75
+
76
+ .modal-input:focus {
77
+ outline: none;
78
+ border-color: var(--color-primary);
79
+ }
80
+
81
+ .modal-actions {
82
+ display: flex;
83
+ justify-content: flex-end;
84
+ gap: var(--hdk-space-sm);
85
+ margin-top: 18px;
86
+ }
87
+
88
+ .modal-button {
89
+ font: inherit;
90
+ font-size: var(--hdk-text-sm);
91
+ padding: 8px 14px;
92
+ border-radius: var(--hdk-radius);
93
+ border: 1px solid var(--color-border);
94
+ background: none;
95
+ color: var(--color-text);
96
+ cursor: pointer;
97
+ }
98
+
99
+ .modal-button:hover:not(:disabled) {
100
+ border-color: var(--color-primary);
101
+ }
102
+
103
+ .modal-button:disabled {
104
+ opacity: 0.5;
105
+ cursor: not-allowed;
106
+ }
107
+
108
+ .modal-button--primary {
109
+ background-color: var(--color-primary);
110
+ border-color: var(--color-primary);
111
+ color: var(--color-on-primary);
112
+ }
113
+
114
+ /* The destructive button carries its accent on the border, not the label.
115
+ * `--color-danger` as bare text on the card misses AA in 11 of 18 themes —
116
+ * the same unvalidated accent-over-surface pair that check-usage now gates. */
117
+ .modal-button--danger {
118
+ border-color: var(--color-danger);
119
+ color: var(--color-text);
120
+ }
121
+
122
+ /* Filled on hover, so the label takes `on-danger` — NOT `on-primary`, which is
123
+ * derived from primary's luminance and misses AA on a danger fill in 9 of 18
124
+ * themes. `on-danger` over `danger` is the pair check-contrast guarantees. */
125
+ .modal-button--danger:hover:not(:disabled) {
126
+ background-color: var(--color-danger);
127
+ border-color: var(--color-danger);
128
+ color: var(--color-on-danger);
129
+ }
@@ -0,0 +1,131 @@
1
+ /* NumberInput styles for @wolffm/task-ui-components.
2
+ *
3
+ * import '@wolffm/task-ui-components/number-input.css'
4
+ *
5
+ * One bordered box holding the field and a stacked up/down stepper, so the
6
+ * focus ring and the border belong to the whole control, as on <Select>.
7
+ */
8
+
9
+ .hdk-number {
10
+ display: inline-flex;
11
+ align-items: stretch;
12
+ min-width: 0;
13
+ vertical-align: middle;
14
+ color: var(--color-text);
15
+ background-color: var(--color-bg-card);
16
+ border: 1px solid var(--color-border);
17
+ border-radius: var(--hdk-radius);
18
+ overflow: hidden;
19
+ transition:
20
+ border-color var(--transition-fast),
21
+ box-shadow var(--transition-fast);
22
+ }
23
+
24
+ .hdk-number:hover:not(.hdk-number--disabled) {
25
+ border-color: var(--color-text-muted);
26
+ }
27
+
28
+ .hdk-number:focus-within {
29
+ border-color: var(--color-primary);
30
+ box-shadow: var(--hdk-shadow-focus-sm);
31
+ }
32
+
33
+ .hdk-number--disabled {
34
+ opacity: 0.5;
35
+ }
36
+
37
+ .hdk-number__input {
38
+ flex: 1;
39
+ width: 100%;
40
+ min-width: 0;
41
+ font: inherit;
42
+ color: inherit;
43
+ background: transparent;
44
+ border: 0;
45
+ appearance: textfield;
46
+ }
47
+
48
+ .hdk-number__input:focus {
49
+ outline: none;
50
+ }
51
+
52
+ .hdk-number__input::-webkit-inner-spin-button,
53
+ .hdk-number__input::-webkit-outer-spin-button {
54
+ appearance: none;
55
+ margin: 0;
56
+ }
57
+
58
+ .hdk-number__steps {
59
+ display: flex;
60
+ flex-direction: column;
61
+ flex-shrink: 0;
62
+ border-left: 1px solid var(--color-border);
63
+ }
64
+
65
+ .hdk-number__step {
66
+ display: flex;
67
+ flex: 1;
68
+ align-items: center;
69
+ justify-content: center;
70
+ padding: 0;
71
+ color: var(--color-text-secondary);
72
+ background: transparent;
73
+ border: 0;
74
+ cursor: pointer;
75
+ transition:
76
+ background-color var(--transition-fast),
77
+ color var(--transition-fast);
78
+ }
79
+
80
+ .hdk-number__step + .hdk-number__step {
81
+ border-top: 1px solid var(--color-border);
82
+ }
83
+
84
+ .hdk-number__step:hover:not(:disabled) {
85
+ color: var(--color-on-primary-bg);
86
+ background-color: var(--color-primary-bg);
87
+ }
88
+
89
+ .hdk-number__step:disabled {
90
+ opacity: 0.4;
91
+ cursor: not-allowed;
92
+ }
93
+
94
+ .hdk-number--sm {
95
+ font-size: var(--hdk-text-xs);
96
+ min-height: 28px;
97
+ }
98
+
99
+ .hdk-number--sm .hdk-number__input {
100
+ padding: 4px 8px;
101
+ }
102
+
103
+ .hdk-number--sm .hdk-number__step {
104
+ width: 18px;
105
+ }
106
+
107
+ .hdk-number--md {
108
+ font-size: var(--hdk-text-sm);
109
+ min-height: 34px;
110
+ }
111
+
112
+ .hdk-number--md .hdk-number__input {
113
+ padding: 7px 10px;
114
+ }
115
+
116
+ .hdk-number--md .hdk-number__step {
117
+ width: 22px;
118
+ }
119
+
120
+ .hdk-number--lg {
121
+ font-size: var(--hdk-text-base);
122
+ min-height: 42px;
123
+ }
124
+
125
+ .hdk-number--lg .hdk-number__input {
126
+ padding: 10px 12px;
127
+ }
128
+
129
+ .hdk-number--lg .hdk-number__step {
130
+ width: 26px;
131
+ }