@human-synthesis/norns-ui 0.0.5 → 0.0.7

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 (50) hide show
  1. package/README.md +32 -20
  2. package/package.json +7 -3
  3. package/src/auto-import.js +1 -4
  4. package/src/components/Accordion.n +24 -23
  5. package/src/components/Autocomplete.n +108 -33
  6. package/src/components/Collapsible.n +8 -11
  7. package/src/components/ContextMenu.n +55 -36
  8. package/src/components/DatePicker.n +19 -31
  9. package/src/components/DateRangePicker.n +17 -29
  10. package/src/components/Dialog.n +35 -22
  11. package/src/components/Dropdown.n +54 -23
  12. package/src/components/MultiSelect.n +138 -36
  13. package/src/components/Popover.n +30 -15
  14. package/src/components/ScrollArea.n +9 -20
  15. package/src/components/Sheet.n +36 -22
  16. package/src/components/Tabs.n +39 -13
  17. package/src/components/TimePicker.n +52 -65
  18. package/src/components/ToggleButton.n +18 -12
  19. package/src/components/ToggleButtonGroup.n +31 -11
  20. package/src/components/Tooltip.n +52 -19
  21. package/src/index.js +1 -4
  22. package/src/lib/behaviors/clickOutside.svelte.js +54 -0
  23. package/src/lib/behaviors/escape.svelte.js +36 -0
  24. package/src/lib/behaviors/floating.svelte.js +137 -0
  25. package/src/lib/behaviors/focusTrap.svelte.js +80 -0
  26. package/src/lib/behaviors/index.js +14 -0
  27. package/src/lib/behaviors/portal.svelte.js +32 -0
  28. package/src/lib/behaviors/rovingTabindex.svelte.js +76 -0
  29. package/src/lib/behaviors/scrollLock.svelte.js +56 -0
  30. package/src/styles/atoms.css +628 -920
  31. package/src/styles/tokens.css +33 -0
  32. package/src/types/Collapsible.d.ts +1 -0
  33. package/src/types/ContextMenu.d.ts +6 -1
  34. package/src/types/DatePicker.d.ts +7 -2
  35. package/src/types/DateRangePicker.d.ts +3 -3
  36. package/src/types/Dialog.d.ts +4 -0
  37. package/src/types/Dropdown.d.ts +2 -1
  38. package/src/types/MultiSelect.d.ts +3 -0
  39. package/src/types/ScrollArea.d.ts +1 -1
  40. package/src/types/Sheet.d.ts +3 -0
  41. package/src/types/Tabs.d.ts +2 -0
  42. package/src/types/ToggleButton.d.ts +2 -1
  43. package/src/types/ToggleButtonGroup.d.ts +2 -1
  44. package/src/types/Tooltip.d.ts +3 -0
  45. package/src/components/Drawer.n +0 -49
  46. package/src/components/PreviewCard.n +0 -30
  47. package/src/components/RichTooltip.n +0 -33
  48. package/src/types/Drawer.d.ts +0 -18
  49. package/src/types/PreviewCard.d.ts +0 -16
  50. package/src/types/RichTooltip.d.ts +0 -16
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Public re-exports of the behavior actions used by the library's components.
3
+ * Consumers can use these directly in their own components for consistency
4
+ * with the library's interaction model.
5
+ *
6
+ * import { portal, clickOutside, escape, focusTrap, useFloating } from '@human-synthesis/norns-ui/behaviors'
7
+ */
8
+ export { portal } from './portal.svelte.js';
9
+ export { clickOutside } from './clickOutside.svelte.js';
10
+ export { escape } from './escape.svelte.js';
11
+ export { focusTrap } from './focusTrap.svelte.js';
12
+ export { lock as scrollLockAcquire, unlock as scrollLockRelease, scrollLock } from './scrollLock.svelte.js';
13
+ export { useFloating, positionAt } from './floating.svelte.js';
14
+ export { rovingTabindex } from './rovingTabindex.svelte.js';
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Svelte action: move a node out of its DOM parent into a target element
3
+ * (default: <body>) for the lifetime of the action. Used by overlays
4
+ * (Dialog, Sheet, Popover, Dropdown, Tooltip) so they're not constrained
5
+ * by parent `overflow` or `transform` stacking contexts.
6
+ *
7
+ * Usage in Pug + Civet:
8
+ * div(use:portal class!="dialog-content")
9
+ *
10
+ * Cleanup note: `destroy` only removes the moved node + the placeholder
11
+ * comment we inserted. It does NOT attempt to move the node back into the
12
+ * original tree. Svelte already iterates its own tracked DOM (and finds
13
+ * the moved node by reference) when the branch unmounts; if we re-inserted
14
+ * the node here, Svelte would leave it in place because it had already
15
+ * "removed" it via the body reference — the overlay would stay visible.
16
+ *
17
+ * @param {HTMLElement} node
18
+ * @param {HTMLElement | string} [target]
19
+ */
20
+ export function portal(node, target = document.body) {
21
+ const dest = typeof target === 'string' ? document.querySelector(target) : target;
22
+ if (!dest) return {};
23
+ const placeholder = document.createComment('portal');
24
+ node.parentNode?.replaceChild(placeholder, node);
25
+ dest.appendChild(node);
26
+ return {
27
+ destroy() {
28
+ node.parentNode?.removeChild(node);
29
+ placeholder.parentNode?.removeChild(placeholder);
30
+ }
31
+ };
32
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Keyboard navigation for menu/tab/listbox patterns.
3
+ *
4
+ * Attach to a container; the action queries focusable items via `selector`
5
+ * (default `[data-roving]`) and routes ArrowDown/Up/Home/End between them.
6
+ * Items get `tabindex=-1` except the active one (`tabindex=0`).
7
+ *
8
+ * ul(use:rovingTabindex={{ orientation: 'vertical', selector: '[data-item]' }})
9
+ *
10
+ * Use `orientation: 'horizontal'` for ArrowLeft/Right (Tabs).
11
+ * Pass `loop: true` to wrap around at the ends.
12
+ */
13
+ export function rovingTabindex(node, opts = {}) {
14
+ let { orientation = 'vertical', selector = '[data-roving]', loop = true } = opts;
15
+ const isVertical = () => orientation === 'vertical';
16
+ const isHorizontal = () => orientation === 'horizontal';
17
+
18
+ const items = () => Array.from(node.querySelectorAll(selector)).filter((el) => !el.disabled);
19
+
20
+ const setTabindex = () => {
21
+ const list = items();
22
+ if (list.length === 0) return;
23
+ const activeIdx = list.indexOf(document.activeElement);
24
+ list.forEach((el, i) => {
25
+ el.setAttribute('tabindex', i === Math.max(0, activeIdx) ? '0' : '-1');
26
+ });
27
+ };
28
+
29
+ const move = (delta) => {
30
+ const list = items();
31
+ if (list.length === 0) return;
32
+ const cur = list.indexOf(document.activeElement);
33
+ let next = cur + delta;
34
+ if (next < 0) next = loop ? list.length - 1 : 0;
35
+ if (next >= list.length) next = loop ? 0 : list.length - 1;
36
+ list[next].focus();
37
+ };
38
+
39
+ const onKeyDown = (e) => {
40
+ const { key } = e;
41
+ if (isVertical() && (key === 'ArrowDown' || key === 'ArrowUp')) {
42
+ e.preventDefault();
43
+ move(key === 'ArrowDown' ? 1 : -1);
44
+ } else if (isHorizontal() && (key === 'ArrowRight' || key === 'ArrowLeft')) {
45
+ e.preventDefault();
46
+ move(key === 'ArrowRight' ? 1 : -1);
47
+ } else if (key === 'Home') {
48
+ e.preventDefault();
49
+ items()[0]?.focus();
50
+ } else if (key === 'End') {
51
+ e.preventDefault();
52
+ const list = items();
53
+ list[list.length - 1]?.focus();
54
+ }
55
+ };
56
+
57
+ const onFocusIn = () => setTabindex();
58
+
59
+ node.addEventListener('keydown', onKeyDown);
60
+ node.addEventListener('focusin', onFocusIn);
61
+ queueMicrotask(setTabindex);
62
+
63
+ return {
64
+ update(next) {
65
+ if (next) {
66
+ orientation = next.orientation ?? orientation;
67
+ selector = next.selector ?? selector;
68
+ loop = next.loop ?? loop;
69
+ }
70
+ },
71
+ destroy() {
72
+ node.removeEventListener('keydown', onKeyDown);
73
+ node.removeEventListener('focusin', onFocusIn);
74
+ }
75
+ };
76
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Refcounted scroll lock for the document body. Multiple overlays may stack
3
+ * (Dialog over Drawer over Popover); each acquires a lock and releases on
4
+ * unmount. The body stays locked until the last release.
5
+ *
6
+ * Preserves the scroll position by setting `position: fixed; top: -<scrollY>`
7
+ * and restoring it on full release.
8
+ */
9
+ let count = 0;
10
+ let savedScrollY = 0;
11
+ let savedStyles = null;
12
+
13
+ export function lock() {
14
+ if (count === 0 && typeof document !== 'undefined') {
15
+ savedScrollY = window.scrollY;
16
+ const body = document.body;
17
+ savedStyles = {
18
+ position: body.style.position,
19
+ top: body.style.top,
20
+ width: body.style.width,
21
+ overflow: body.style.overflow
22
+ };
23
+ body.style.position = 'fixed';
24
+ body.style.top = `-${savedScrollY}px`;
25
+ body.style.width = '100%';
26
+ body.style.overflow = 'hidden';
27
+ }
28
+ count++;
29
+ }
30
+
31
+ export function unlock() {
32
+ if (count === 0) return;
33
+ count--;
34
+ if (count === 0 && savedStyles && typeof document !== 'undefined') {
35
+ const body = document.body;
36
+ body.style.position = savedStyles.position;
37
+ body.style.top = savedStyles.top;
38
+ body.style.width = savedStyles.width;
39
+ body.style.overflow = savedStyles.overflow;
40
+ window.scrollTo(0, savedScrollY);
41
+ savedStyles = null;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Svelte action — locks while mounted, releases on destroy.
47
+ * div(use:scrollLock)
48
+ */
49
+ export function scrollLock(_node) {
50
+ lock();
51
+ return {
52
+ destroy() {
53
+ unlock();
54
+ }
55
+ };
56
+ }