@marianmeres/stuic 1.11.0 → 1.12.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.
@@ -1,4 +1,9 @@
1
- export declare function focusTrap(node: HTMLElement, enabled: boolean): {
2
- update(newArgs: boolean): void;
1
+ interface FocusTrapOptions {
2
+ enabled?: boolean;
3
+ autoFocusFirst?: boolean;
4
+ }
5
+ export declare function focusTrap(node: HTMLElement, options?: FocusTrapOptions): {
6
+ update(options?: FocusTrapOptions): void;
3
7
  destroy(): void;
4
8
  };
9
+ export {};
@@ -1,52 +1,78 @@
1
1
  // copied from skeleton
2
+ const defaults = { enabled: true, autoFocusFirst: false };
2
3
  // Action: Focus Trap
3
- export function focusTrap(node, enabled) {
4
- const elemWhitelist = 'a[href]:not([tabindex="-1"]), button:not([tabindex="-1"]), input:not([tabindex="-1"]), textarea:not([tabindex="-1"]), select:not([tabindex="-1"]), details:not([tabindex="-1"]), [tabindex]:not([tabindex="-1"])';
5
- let elemFirst;
6
- let elemLast;
4
+ export function focusTrap(node, options = {}) {
5
+ let { enabled, autoFocusFirst } = { ...defaults, ...(options || {}) };
6
+ const focusableSelectors = [
7
+ 'a[href]',
8
+ 'area[href]',
9
+ 'details',
10
+ 'iframe',
11
+ 'button:not([disabled])',
12
+ 'input:not([disabled])',
13
+ 'select:not([disabled])',
14
+ 'textarea:not([disabled])',
15
+ '[contentEditable=true]',
16
+ '[tabindex]',
17
+ ].join(',');
18
+ let first;
19
+ let last;
7
20
  // When the first element is selected, shift+tab pressed, jump to the last selectable item.
8
21
  function onFirstElemKeydown(e) {
9
22
  if (e.shiftKey && e.code === 'Tab') {
10
23
  e.preventDefault();
11
- elemLast.focus();
24
+ last.focus();
12
25
  }
13
26
  }
14
27
  // When the last item selected, tab pressed, jump to the first selectable item.
15
28
  function onLastElemKeydown(e) {
16
29
  if (!e.shiftKey && e.code === 'Tab') {
17
30
  e.preventDefault();
18
- elemFirst.focus();
31
+ first.focus();
19
32
  }
20
33
  }
21
- const onScanElements = (fromObserver) => {
34
+ const queryElements = (fromObserver) => {
22
35
  if (enabled === false)
23
36
  return;
24
- // Gather all focusable elements
25
- const focusableElems = Array.from(node.querySelectorAll(elemWhitelist));
26
- if (focusableElems.length) {
27
- // Set first/last focusable elements
28
- elemFirst = focusableElems[0];
29
- elemLast = focusableElems[focusableElems.length - 1];
37
+ const focusable = [...node.querySelectorAll(focusableSelectors)]
38
+ // i am unable to get the selectors right (still getting false positives),
39
+ // but filtering manually works well
40
+ .filter((e) => {
41
+ if (e.getAttribute('disabled') === '')
42
+ return false;
43
+ if ((e.getAttribute('tabindex') || '').startsWith('-'))
44
+ return false;
45
+ return true;
46
+ })
47
+ // sort by tabindex, so the first/last will work as expected
48
+ .toSorted((e1, e2) => {
49
+ let a = parseInt(e1.getAttribute('tabindex') || '0');
50
+ let b = parseInt(e2.getAttribute('tabindex') || '0');
51
+ return a - b;
52
+ });
53
+ if (focusable.length) {
54
+ first = focusable[0];
55
+ last = focusable[focusable.length - 1];
30
56
  // Auto-focus first focusable element only when not called from observer
31
- if (!fromObserver)
32
- elemFirst.focus();
57
+ if (!fromObserver && autoFocusFirst)
58
+ first.focus();
33
59
  // Listen for keydown on first & last element
34
- elemFirst.addEventListener('keydown', onFirstElemKeydown);
35
- elemLast.addEventListener('keydown', onLastElemKeydown);
60
+ first.addEventListener('keydown', onFirstElemKeydown);
61
+ last.addEventListener('keydown', onLastElemKeydown);
36
62
  }
37
63
  };
38
- onScanElements(false);
39
- function onCleanUp() {
40
- if (elemFirst)
41
- elemFirst.removeEventListener('keydown', onFirstElemKeydown);
42
- if (elemLast)
43
- elemLast.removeEventListener('keydown', onLastElemKeydown);
64
+ queryElements(false);
65
+ function cleanup() {
66
+ if (first)
67
+ first.removeEventListener('keydown', onFirstElemKeydown);
68
+ if (last)
69
+ last.removeEventListener('keydown', onLastElemKeydown);
44
70
  }
45
71
  // When children of node are changed (added or removed)
46
72
  const onObservationChange = (mutationRecords, observer) => {
47
73
  if (mutationRecords.length) {
48
- onCleanUp();
49
- onScanElements(true);
74
+ cleanup();
75
+ queryElements(true);
50
76
  }
51
77
  return observer;
52
78
  };
@@ -54,12 +80,11 @@ export function focusTrap(node, enabled) {
54
80
  observer.observe(node, { childList: true, subtree: true });
55
81
  // Lifecycle
56
82
  return {
57
- update(newArgs) {
58
- enabled = newArgs;
59
- newArgs ? onScanElements(false) : onCleanUp();
83
+ update(options = {}) {
84
+ options?.enabled ? queryElements(false) : cleanup();
60
85
  },
61
86
  destroy() {
62
- onCleanUp();
87
+ cleanup();
63
88
  observer.disconnect();
64
89
  },
65
90
  };
@@ -43,7 +43,7 @@ $:
43
43
  on:keydown={(e) => e.code === 'Escape' && dispatch('escape')}
44
44
  in:fade={{ duration: fadeInDuration }}
45
45
  out:fade={{ duration: fadeOutDuration }}
46
- use:focusTrap={useFocusTrap}
46
+ use:focusTrap={{ enabled: useFocusTrap }}
47
47
  role="presentation"
48
48
  tabindex="-1"
49
49
  >
@@ -4,18 +4,20 @@ export class ButtonConfig {
4
4
  static defaultShadow = false;
5
5
  static defaultRounded = true;
6
6
  static defaultVariant = void 0;
7
- // hover:brightness-[1.15]
8
7
  static presetBase = `
9
- text-base
10
8
  text-center whitespace-nowrap
11
- inline-flex justify-center items-center gap-x-1
12
- border border-transparent
13
- hover:brightness-[1.1]
9
+ inline-flex items-center gap-x-2
10
+ hover:brightness-[1.05]
14
11
  active:brightness-[0.95]
15
12
  disabled:!cursor-not-allowed disabled:!opacity-50 disabled:hover:brightness-100
16
13
  no-underline
14
+ border
15
+
16
+ bg-zinc-200 text-black
17
+ dark:bg-zinc-600 dark:text-white
18
+ border-zinc-400 dark:border-zinc-500
17
19
  `.trim();
18
- static presetSquare = "p-0 aspect-square";
20
+ static presetSquare = "p-0 aspect-square justify-center";
19
21
  static presetsRounded = {
20
22
  xs: "rounded",
21
23
  sm: "rounded",
@@ -31,11 +33,11 @@ export class ButtonConfig {
31
33
  xl: "shadow-md dark:shadow-black"
32
34
  };
33
35
  static presetsSize = {
34
- xs: "px-2 py-0.5 text-xs",
35
- sm: "px-3 py-1 text-sm",
36
- md: "px-3.5 py-1 text-base",
37
- lg: "px-4 py-1.5 text-lg",
38
- xl: "px-4 py-2 text-xl"
36
+ xs: "px-2 py-0.5 leading-tight text-xs",
37
+ sm: "px-2.5 py-0.5 leading-snug text-sm",
38
+ md: "px-3 py-1 leading-normal text-base",
39
+ lg: "px-4 py-1.5 leading-relaxed text-lg",
40
+ xl: "px-4 py-2 leading text-xl"
39
41
  };
40
42
  static classBySize = {
41
43
  xs: "",
@@ -1,7 +1,7 @@
1
1
  <script>import { twMerge } from "tailwind-merge";
2
2
  let _class = "";
3
3
  export { _class as class };
4
- export let size = "1em";
4
+ export let size = "1rem";
5
5
  </script>
6
6
 
7
7
  <svg
package/dist/index.d.ts CHANGED
@@ -7,5 +7,7 @@ export { ColorScheme } from './components/ColorScheme/color-scheme.js';
7
7
  export { default as Drawer, createDrawerStore } from './components/Drawer/Drawer.svelte';
8
8
  export { default as HoverExpandableWidth } from './components/HoverExpandableWidth/HoverExpandableWidth.svelte';
9
9
  export { default as X } from './components/X/X.svelte';
10
+ export { clickOutside } from './actions/click-outside.js';
11
+ export { focusTrap } from './actions/focus-trap.js';
10
12
  export { windowSize, breakpoint } from './utils/window-size.js';
11
13
  export { DevicePointer } from './utils/device-pointer.js';
package/dist/index.js CHANGED
@@ -15,6 +15,9 @@ export { default as Drawer, createDrawerStore } from './components/Drawer/Drawer
15
15
  export { default as HoverExpandableWidth } from './components/HoverExpandableWidth/HoverExpandableWidth.svelte';
16
16
  //
17
17
  export { default as X } from './components/X/X.svelte';
18
+ // actions
19
+ export { clickOutside } from './actions/click-outside.js';
20
+ export { focusTrap } from './actions/focus-trap.js';
18
21
  // utils
19
22
  export { windowSize, breakpoint } from './utils/window-size.js';
20
23
  export { DevicePointer } from './utils/device-pointer.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run package",