@geoffcox/sterling-svelte 0.0.25 → 0.0.27

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 (86) hide show
  1. package/Button.svelte +79 -24
  2. package/Button.svelte.d.ts +1 -0
  3. package/Checkbox.svelte +44 -19
  4. package/Checkbox.svelte.d.ts +1 -0
  5. package/ColorPicker.constants.d.ts +1 -0
  6. package/ColorPicker.constants.js +1 -0
  7. package/ColorPicker.svelte +226 -0
  8. package/ColorPicker.svelte.d.ts +22 -0
  9. package/ColorPicker.types.d.ts +4 -0
  10. package/ColorPicker.types.js +1 -0
  11. package/Dialog.svelte +10 -10
  12. package/Dropdown.svelte +88 -47
  13. package/Dropdown.svelte.d.ts +4 -0
  14. package/Field.svelte +34 -46
  15. package/HexColorSliders.svelte +150 -0
  16. package/HexColorSliders.svelte.d.ts +22 -0
  17. package/HslColorSliders.svelte +187 -0
  18. package/HslColorSliders.svelte.d.ts +22 -0
  19. package/Input.svelte +49 -21
  20. package/Input.svelte.d.ts +2 -1
  21. package/Label.svelte +3 -3
  22. package/Link.svelte +63 -17
  23. package/Link.svelte.d.ts +1 -0
  24. package/List.svelte +31 -30
  25. package/List.svelte.d.ts +1 -0
  26. package/List.types.d.ts +4 -3
  27. package/ListItem.svelte +29 -10
  28. package/ListItem.svelte.d.ts +1 -1
  29. package/Menu.svelte +92 -121
  30. package/Menu.svelte.d.ts +8 -2
  31. package/MenuBar.svelte +77 -32
  32. package/MenuBar.types.d.ts +2 -2
  33. package/MenuButton.svelte +48 -28
  34. package/MenuItem.constants.d.ts +1 -0
  35. package/MenuItem.constants.js +1 -0
  36. package/MenuItem.svelte +202 -139
  37. package/MenuItem.svelte.d.ts +7 -3
  38. package/MenuItem.types.d.ts +14 -5
  39. package/MenuItem.utils.d.ts +2 -0
  40. package/MenuItem.utils.js +16 -0
  41. package/MenuItemDisplay.svelte +9 -2
  42. package/MenuItemDisplay.svelte.d.ts +1 -0
  43. package/MenuSeparator.svelte +3 -3
  44. package/Popover.svelte +68 -64
  45. package/Popover.svelte.d.ts +4 -2
  46. package/Progress.svelte +14 -14
  47. package/Radio.svelte +42 -16
  48. package/Radio.svelte.d.ts +1 -0
  49. package/RgbColorSliders.svelte +161 -0
  50. package/RgbColorSliders.svelte.d.ts +22 -0
  51. package/Select.svelte +50 -32
  52. package/Slider.svelte +108 -118
  53. package/Slider.svelte.d.ts +1 -0
  54. package/Switch.svelte +97 -34
  55. package/Switch.svelte.d.ts +1 -0
  56. package/Tab.svelte +53 -30
  57. package/TabList.svelte +23 -28
  58. package/TabList.svelte.d.ts +1 -0
  59. package/TabList.types.d.ts +1 -1
  60. package/TextArea.svelte +45 -20
  61. package/TextArea.svelte.d.ts +3 -2
  62. package/Tooltip.svelte +12 -11
  63. package/Tree.svelte +37 -35
  64. package/Tree.svelte.d.ts +2 -0
  65. package/Tree.types.d.ts +1 -0
  66. package/TreeChevron.svelte +1 -1
  67. package/TreeItem.svelte +47 -10
  68. package/TreeItem.svelte.d.ts +2 -0
  69. package/TreeItemDisplay.svelte +26 -8
  70. package/TreeItemDisplay.svelte.d.ts +2 -0
  71. package/actions/clickOutside.js +1 -1
  72. package/actions/trapKeyboardFocus.d.ts +3 -0
  73. package/actions/trapKeyboardFocus.js +52 -0
  74. package/floating-ui.types.d.ts +2 -0
  75. package/index.d.ts +10 -5
  76. package/index.js +8 -3
  77. package/package.json +12 -1
  78. package/stores/prefersReducedMotion.d.ts +1 -0
  79. package/stores/prefersReducedMotion.js +10 -0
  80. package/stores/usingKeyboard.d.ts +1 -0
  81. package/stores/usingKeyboard.js +13 -0
  82. package/theme/applyTheme.js +3 -2
  83. package/theme/colors.d.ts +1 -0
  84. package/theme/colors.js +28 -13
  85. package/theme/darkTheme.js +130 -87
  86. package/theme/lightTheme.js +107 -87
package/ListItem.svelte CHANGED
@@ -1,16 +1,18 @@
1
1
  <script>import { getContext } from "svelte";
2
2
  import { LIST_CONTEXT_KEY } from "./List.constants";
3
- import { readable } from "svelte/store";
3
+ import { readable, writable } from "svelte/store";
4
4
  export let disabled = false;
5
5
  export let value;
6
6
  const {
7
+ colorful,
7
8
  disabled: listDisabled,
8
9
  selectedValue,
9
10
  horizontal
10
11
  } = getContext(LIST_CONTEXT_KEY) || {
12
+ colorful: readable(false),
11
13
  disabled: readable(false),
12
- selectedValue: void 0,
13
- horizontal: false
14
+ selectedValue: writable(void 0),
15
+ horizontal: readable(false)
14
16
  };
15
17
  let itemRef;
16
18
  $:
@@ -30,6 +32,7 @@ export const focus = (options) => {
30
32
  aria-selected={selected}
31
33
  bind:this={itemRef}
32
34
  class="sterling-list-item"
35
+ class:colorful={$colorful}
33
36
  class:disabled={disabled || $listDisabled}
34
37
  class:item-disabled={disabled && !$listDisabled}
35
38
  class:selected
@@ -65,7 +68,7 @@ export const focus = (options) => {
65
68
  on:pointerover
66
69
  on:pointerout
67
70
  on:pointerup
68
- on:wheel
71
+ on:wheel|passive
69
72
  {...$$restProps}
70
73
  >
71
74
  <slot {disabled} {horizontal} {selected} {value}>{value}</slot>
@@ -75,7 +78,7 @@ export const focus = (options) => {
75
78
  .sterling-list-item {
76
79
  background-color: transparent;
77
80
  box-sizing: border-box;
78
- color: var(--stsv-Input__color);
81
+ color: var(--stsv-common__color);
79
82
  cursor: pointer;
80
83
  margin: 0;
81
84
  padding: 0.5em;
@@ -87,13 +90,23 @@ export const focus = (options) => {
87
90
  }
88
91
 
89
92
  .sterling-list-item:not(.disabled):not(.selected):hover {
90
- background-color: var(--stsv-Button__background-color--hover);
91
- color: var(--stsv-Button__color--hover);
93
+ background-color: var(--stsv-button__background-color--hover);
94
+ color: var(--stsv-button__color--hover);
92
95
  }
93
96
 
94
97
  .sterling-list-item.selected {
95
- background-color: var(--stsv-Input__background-color--selected);
96
- color: var(--stsv-Input__color--selected);
98
+ background-color: var(--stsv-button__background-color--active);
99
+ color: var(--stsv-button__color--active);
100
+ }
101
+
102
+ .sterling-list-item.colorful:not(.disabled):not(.selected):hover {
103
+ background-color: var(--stsv-button--colorful__background-color--hover);
104
+ color: var(--stsv-button--colorful__color--hover);
105
+ }
106
+
107
+ .sterling-list-item.colorful.selected {
108
+ background-color: var(--stsv-button--colorful__background-color--active);
109
+ color: var(--stsv-button--colorful__color--active);
97
110
  }
98
111
 
99
112
  .sterling-list-item.disabled {
@@ -102,7 +115,13 @@ export const focus = (options) => {
102
115
  }
103
116
 
104
117
  .sterling-list-item::after {
105
- background: var(--stsv-Disabled__background);
118
+ background: repeating-linear-gradient(
119
+ 45deg,
120
+ var(--stsv-common__background-color1--disabled),
121
+ var(--stsv-common__background-color1--disabled) 3px,
122
+ var(--stsv-common__background-color2--disabled) 3px,
123
+ var(--stsv-common__background-color2--disabled) 6px
124
+ );
106
125
  bottom: 0;
107
126
  content: '';
108
127
  left: 0;
@@ -46,7 +46,7 @@ declare const __propDef: {
46
46
  slots: {
47
47
  default: {
48
48
  disabled: boolean;
49
- horizontal: import("svelte/store").Readable<boolean>;
49
+ horizontal: import("svelte/store").Readable<boolean> | undefined;
50
50
  selected: boolean;
51
51
  value: string;
52
52
  };
package/Menu.svelte CHANGED
@@ -1,155 +1,126 @@
1
- <script>import { getContext, onMount } from "svelte";
2
- import { autoUpdate, computePosition, flip, offset, shift } from "@floating-ui/dom";
3
- import { portal } from "./actions/portal";
1
+ <script>import { getContext } from "svelte";
2
+ import { slide } from "svelte/transition";
4
3
  import { MENU_ITEM_CONTEXT_KEY } from "./MenuItem.constants";
5
- export let reference;
6
- export let open = false;
4
+ import { isElementEnabledMenuItem, isElementMenuItem } from "./MenuItem.utils";
5
+ import { prefersReducedMotion } from "./stores/prefersReducedMotion";
7
6
  let menuRef;
8
- let menuPosition = { x: 0, y: 0 };
9
- const { rootValue, depth = 0 } = getContext(MENU_ITEM_CONTEXT_KEY) || {};
7
+ let menuItemsRef;
8
+ const slidNoOp = (node, params) => {
9
+ return { delay: 0, duration: 0 };
10
+ };
11
+ $:
12
+ slideMotion = !$prefersReducedMotion ? slide : slidNoOp;
13
+ const { rootValue } = getContext(MENU_ITEM_CONTEXT_KEY) || {};
10
14
  export const blur = () => {
11
15
  menuRef?.blur();
12
16
  };
13
17
  export const focus = (options) => {
14
18
  menuRef?.focus(options);
15
19
  };
16
- const ensurePortalHost = () => {
17
- let host = document.querySelector("#SterlingMenuPortal");
18
- if (!host) {
19
- host = document.createElement("div");
20
- host.id = "SterlingMenuPortal";
21
- host.style.overflow = "visible";
22
- document.body.append(host);
20
+ const isElementInThisMenu = (candidate) => {
21
+ return candidate && candidate.closest('[role="menu"]') === menuRef;
22
+ };
23
+ export const focusFirstMenuItem = () => {
24
+ let candidate = menuItemsRef?.firstElementChild;
25
+ while (candidate && !isElementEnabledMenuItem(candidate)) {
26
+ candidate = candidate.nextElementSibling;
23
27
  }
24
- return host;
28
+ candidate?.focus({ preventScroll: true });
29
+ return !!candidate;
25
30
  };
26
- const portalTarget = ensurePortalHost();
27
- let bodyHeight = 0;
28
- const resizeObserver = new ResizeObserver((entries) => {
29
- bodyHeight = entries[0].target.clientHeight;
30
- });
31
- const menuPlacement = depth > 1 ? "right-start" : "bottom-start";
32
- const middleware = [
33
- offset({ mainAxis: -2 }),
34
- flip(),
35
- shift({ padding: 0, mainAxis: true, crossAxis: true })
36
- ];
37
- const computeMenuPosition = async () => {
38
- if (reference && menuRef) {
39
- menuPosition = await computePosition(reference, menuRef, {
40
- placement: menuPlacement,
41
- middleware
42
- });
43
- } else {
44
- menuPosition = { x: 0, y: 0 };
31
+ export const focusPreviousMenuItem = () => {
32
+ let candidate = document.activeElement;
33
+ if (candidate && isElementMenuItem(candidate) && isElementInThisMenu(candidate)) {
34
+ candidate = menuItemsRef?.previousElementSibling;
35
+ while (candidate && !isElementEnabledMenuItem(candidate)) {
36
+ candidate = candidate.previousElementSibling;
37
+ }
38
+ candidate?.focus();
45
39
  }
40
+ return !!candidate;
46
41
  };
47
- let cleanupAutoUpdate = () => {
42
+ export const focusNextMenuItem = () => {
43
+ let candidate = document.activeElement;
44
+ if (candidate && isElementMenuItem(candidate) && isElementInThisMenu(candidate)) {
45
+ candidate = menuItemsRef?.nextElementSibling;
46
+ while (candidate && !isElementEnabledMenuItem(candidate)) {
47
+ candidate = candidate.nextElementSibling;
48
+ }
49
+ candidate?.focus();
50
+ }
51
+ return !!candidate;
48
52
  };
49
- const autoUpdateMenuPosition = () => {
50
- cleanupAutoUpdate();
51
- if (reference && menuRef) {
52
- cleanupAutoUpdate = autoUpdate(reference, menuRef, computeMenuPosition);
53
+ export const focusLastMenuItem = () => {
54
+ let candidate = menuItemsRef?.lastElementChild;
55
+ while (candidate && !isElementEnabledMenuItem(candidate)) {
56
+ candidate = candidate.previousElementSibling;
53
57
  }
58
+ candidate?.focus({ preventScroll: true });
59
+ return !!candidate;
54
60
  };
55
- $:
56
- menuRef, reference, autoUpdateMenuPosition();
57
- $:
58
- open, bodyHeight, computeMenuPosition();
59
- onMount(() => {
60
- resizeObserver.observe(document.body);
61
- return () => {
62
- resizeObserver.unobserve(document.body);
63
- };
64
- });
65
61
  </script>
66
62
 
67
- {#if open}
68
- <div
69
- use:portal={{ target: portalTarget }}
70
- class="sterling-menu-portal"
71
- data-root-value={rootValue}
72
- >
73
- <div
74
- bind:this={menuRef}
75
- class="sterling-menu"
76
- role="menu"
77
- class:open
78
- on:blur
79
- on:click
80
- on:copy
81
- on:cut
82
- on:dblclick
83
- on:dragend
84
- on:dragenter
85
- on:dragleave
86
- on:dragover
87
- on:dragstart
88
- on:drop
89
- on:focus
90
- on:focusin
91
- on:focusout
92
- on:keydown
93
- on:keypress
94
- on:keyup
95
- on:mousedown
96
- on:mouseenter
97
- on:mouseleave
98
- on:mousemove
99
- on:mouseover
100
- on:mouseout
101
- on:mouseup
102
- on:scroll
103
- on:wheel
104
- on:paste
105
- {...$$restProps}
106
- style="left:{menuPosition.x}px; top:{menuPosition.y}px"
107
- >
108
- <div class="sterling-menu-content">
109
- <slot />
110
- </div>
111
- </div>
63
+ <div
64
+ bind:this={menuRef}
65
+ class="sterling-menu"
66
+ role="menu"
67
+ class:open
68
+ data-root-value={rootValue}
69
+ in:slideMotion={{ duration: 300 }}
70
+ out:slideMotion={{ duration: 100 }}
71
+ on:blur
72
+ on:click
73
+ on:copy
74
+ on:cut
75
+ on:dblclick
76
+ on:dragend
77
+ on:dragenter
78
+ on:dragleave
79
+ on:dragover
80
+ on:dragstart
81
+ on:drop
82
+ on:focus
83
+ on:focusin
84
+ on:focusout
85
+ on:keydown
86
+ on:keypress
87
+ on:keyup
88
+ on:mousedown
89
+ on:mouseenter
90
+ on:mouseleave
91
+ on:mousemove
92
+ on:mouseover
93
+ on:mouseout
94
+ on:mouseup
95
+ on:scroll
96
+ on:wheel|passive
97
+ on:paste
98
+ {...$$restProps}
99
+ >
100
+ <div bind:this={menuItemsRef} class="menu-items">
101
+ <slot />
112
102
  </div>
113
- {/if}
103
+ </div>
114
104
 
115
105
  <style>
116
- .sterling-menu-portal {
117
- position: relative;
118
- overflow: visible;
119
- background: rgba(255, 0, 0, 0.1);
120
- }
121
-
122
106
  .sterling-menu {
123
- background-color: var(--stsv-Common__background-color);
124
- border-color: var(--stsv-Common__border-color);
125
- border-radius: var(--stsv-Common__border-radius);
126
- border-style: var(--stsv-Common__border-style);
127
- border-width: var(--stsv-Common__border-width);
128
- box-shadow: rgba(0, 0, 0, 0.4) 2px 2px 4px -1px;
107
+ background-color: var(--stsv-common__background-color);
108
+ border-color: var(--stsv-common__border-color);
109
+ border-radius: var(--stsv-common__border-radius);
110
+ border-style: var(--stsv-common__border-style);
111
+ border-width: var(--stsv-common__border-width);
129
112
  box-sizing: border-box;
130
113
  display: grid;
131
114
  grid-template-columns: 1fr;
132
115
  grid-template-rows: 1fr;
133
116
  height: fit-content;
134
- left: 0;
135
- max-height: calc(50vh);
136
- overflow: auto;
137
- overscroll-behavior: contain;
138
117
  padding: 0.25em;
139
- position: absolute;
140
- top: 0;
141
- width: max-content;
142
- z-index: 1;
143
118
  }
144
119
 
145
- .sterling-menu-content {
120
+ .menu-items {
146
121
  display: grid;
147
122
  grid-template-columns: 1fr;
148
123
  grid-template-rows: auto;
149
- row-gap: calc(2 * var(--stsv-Common__outline-width));
150
- }
151
-
152
- .sterling-menu.open {
153
- display: grid;
124
+ row-gap: calc(2 * var(--stsv-common__outline-width));
154
125
  }
155
126
  </style>
package/Menu.svelte.d.ts CHANGED
@@ -2,10 +2,12 @@ import { SvelteComponentTyped } from "svelte";
2
2
  declare const __propDef: {
3
3
  props: {
4
4
  [x: string]: any;
5
- reference: HTMLElement;
6
- open?: boolean | undefined;
7
5
  blur?: (() => void) | undefined;
8
6
  focus?: ((options?: FocusOptions) => void) | undefined;
7
+ focusFirstMenuItem?: (() => boolean) | undefined;
8
+ focusPreviousMenuItem?: (() => boolean) | undefined;
9
+ focusNextMenuItem?: (() => boolean) | undefined;
10
+ focusLastMenuItem?: (() => boolean) | undefined;
9
11
  };
10
12
  events: {
11
13
  blur: FocusEvent;
@@ -48,5 +50,9 @@ export type MenuSlots = typeof __propDef.slots;
48
50
  export default class Menu extends SvelteComponentTyped<MenuProps, MenuEvents, MenuSlots> {
49
51
  get blur(): () => void;
50
52
  get focus(): (options?: FocusOptions | undefined) => void;
53
+ get focusFirstMenuItem(): () => boolean;
54
+ get focusPreviousMenuItem(): () => boolean;
55
+ get focusNextMenuItem(): () => boolean;
56
+ get focusLastMenuItem(): () => boolean;
51
57
  }
52
58
  export {};
package/MenuBar.svelte CHANGED
@@ -2,7 +2,25 @@
2
2
  import { MENU_BAR_CONTEXT_KEY } from "./MenuBar.constants";
3
3
  import { MENU_ITEM_CONTEXT_KEY } from "./MenuItem.constants";
4
4
  import { writable } from "svelte/store";
5
+ import { isElementEnabledMenuItem } from "./MenuItem.utils";
6
+ import { idGenerator } from "./idGenerator";
7
+ import { clickOutside } from "./actions/clickOutside";
8
+ const openValues = writable([]);
9
+ const rootValue = idGenerator.nextId("MenuBar");
5
10
  let menuBarRef;
11
+ let prevOpenValue = void 0;
12
+ $: {
13
+ if ($openValues.length > 0) {
14
+ prevOpenValue = $openValues[0];
15
+ }
16
+ }
17
+ $: {
18
+ if ($openValues.length === 0 && prevOpenValue !== void 0) {
19
+ const candidate = menuBarRef.querySelector(`[data-value="${prevOpenValue}"]`);
20
+ candidate?.focus();
21
+ prevOpenValue = void 0;
22
+ }
23
+ }
6
24
  const dispatch = createEventDispatcher();
7
25
  const raiseClose = (value) => {
8
26
  dispatch("close", { value });
@@ -19,48 +37,73 @@ export const blur = () => {
19
37
  export const focus = (options) => {
20
38
  menuBarRef?.focus(options);
21
39
  };
22
- const children = writable([]);
23
- const openPreviousChild = (currentValue) => {
24
- const index = $children?.findIndex((menuItem) => menuItem.value === currentValue);
25
- if (index !== -1) {
26
- const focusIndex = index === 0 ? $children.length - 1 : index - 1;
27
- $children[focusIndex].focus();
28
- $children[focusIndex].open();
40
+ const getOpenMenuBarItem = () => {
41
+ const value = $openValues[0];
42
+ if (value) {
43
+ return menuBarRef.querySelector(`[data-value="${value}"]`);
29
44
  }
45
+ return null;
30
46
  };
31
- const openNextChild = (currentValue) => {
32
- const index = $children?.findIndex((menuItem) => menuItem.value === currentValue);
33
- if (index !== -1) {
34
- const focusIndex = (index + 1) % $children.length;
35
- $children[focusIndex].focus();
36
- $children[focusIndex].open();
47
+ const openPreviousMenuBarItem = () => {
48
+ const openItem = getOpenMenuBarItem() || menuBarRef.firstElementChild;
49
+ let candidate = openItem?.previousElementSibling || menuBarRef.lastElementChild;
50
+ while (candidate && !isElementEnabledMenuItem(candidate)) {
51
+ candidate = candidate.previousElementSibling || menuBarRef.lastElementChild;
52
+ if (candidate === openItem) {
53
+ return false;
54
+ }
55
+ }
56
+ if (!candidate) {
57
+ candidate = menuBarRef.lastElementChild;
58
+ candidate = candidate && isElementEnabledMenuItem(candidate) ? candidate : null;
37
59
  }
60
+ candidate?.click();
61
+ return !!candidate;
38
62
  };
39
- const focusChild = (value) => {
40
- const focusIndex = $children?.findIndex((menuItem) => menuItem.value === value);
41
- if (focusIndex !== -1) {
42
- $children[focusIndex].focus();
63
+ const openNextMenuBarItem = () => {
64
+ const openItem = getOpenMenuBarItem() || menuBarRef.lastElementChild;
65
+ let candidate = openItem?.nextElementSibling || menuBarRef.firstElementChild;
66
+ while (candidate && !isElementEnabledMenuItem(candidate)) {
67
+ candidate = candidate.nextElementSibling || menuBarRef.firstElementChild;
68
+ if (candidate === openItem) {
69
+ return false;
70
+ }
43
71
  }
72
+ if (!candidate) {
73
+ candidate = menuBarRef.firstElementChild;
74
+ candidate = candidate && isElementEnabledMenuItem(candidate) ? candidate : null;
75
+ }
76
+ candidate?.click();
77
+ return !!candidate;
78
+ };
79
+ const closeAllMenus = () => {
80
+ openValues.set([]);
44
81
  };
82
+ const onClickOutside = (event) => {
83
+ const {
84
+ detail: { mouseEvent }
85
+ } = event;
86
+ let element = mouseEvent.target;
87
+ while (element) {
88
+ if (element.getAttribute("data-root-value") === rootValue) {
89
+ return;
90
+ }
91
+ element = element.parentElement;
92
+ }
93
+ closeAllMenus?.();
94
+ };
95
+ setContext(MENU_BAR_CONTEXT_KEY, {
96
+ openPreviousMenuBarItem,
97
+ openNextMenuBarItem
98
+ });
45
99
  setContext(MENU_ITEM_CONTEXT_KEY, {
46
- register: (menuItem) => {
47
- children.set([...$children, menuItem]);
48
- },
49
- unregister: (menuItem) => {
50
- children.set($children.filter((x) => x.value !== menuItem.value));
51
- },
52
- closeMenu: (recursive) => {
53
- },
54
- focusPrevious: openPreviousChild,
55
- focusNext: openNextChild,
100
+ isMenuBarItem: true,
101
+ openValues,
102
+ rootValue,
56
103
  onClose: raiseClose,
57
104
  onOpen: raiseOpen,
58
105
  onSelect: raiseSelect
59
106
  });
60
- setContext(MENU_BAR_CONTEXT_KEY, {
61
- openPreviousMenu: openPreviousChild,
62
- openNextMenu: openNextChild
63
- });
64
107
  </script>
65
108
 
66
109
  <div
@@ -92,9 +135,11 @@ setContext(MENU_BAR_CONTEXT_KEY, {
92
135
  on:mouseout
93
136
  on:mouseup
94
137
  on:scroll
95
- on:wheel
138
+ on:wheel|passive
96
139
  on:paste
97
140
  {...$$restProps}
141
+ use:clickOutside
142
+ on:click_outside={onClickOutside}
98
143
  >
99
144
  <slot />
100
145
  </div>
@@ -1,4 +1,4 @@
1
1
  export type MenuBarContext = {
2
- openPreviousMenu?: (currentValue: string) => void;
3
- openNextMenu?: (currentValue: string) => void;
2
+ openPreviousMenuBarItem?: () => void;
3
+ openNextMenuBarItem?: () => void;
4
4
  };
package/MenuButton.svelte CHANGED
@@ -1,10 +1,11 @@
1
- <script>import { createEventDispatcher, getContext, setContext } from "svelte";
1
+ <script>import { createEventDispatcher, setContext, tick } from "svelte";
2
2
  import { writable } from "svelte/store";
3
3
  import Button from "./Button.svelte";
4
4
  import Menu from "./Menu.svelte";
5
5
  import { MENU_ITEM_CONTEXT_KEY } from "./MenuItem.constants";
6
- import { focusFirstChild, focusNextChild, focusPreviousChild } from "./MenuItem.utils";
7
6
  import { idGenerator } from "./idGenerator";
7
+ import Popover from "./Popover.svelte";
8
+ import { clickOutside } from "./actions/clickOutside";
8
9
  export let open = false;
9
10
  export let shape = "rounded";
10
11
  export let value;
@@ -12,12 +13,16 @@ export let variant = "regular";
12
13
  const instanceId = idGenerator.nextId("MenuButton");
13
14
  let buttonRef;
14
15
  let reference;
16
+ let menuRef;
15
17
  let prevOpen = open;
16
18
  $:
17
19
  menuId = `${value}-menu-${instanceId}`;
18
20
  $:
19
21
  hasChildren = $$slots.items;
20
- const children = writable([]);
22
+ const openValues = writable([]);
23
+ $: {
24
+ open = $openValues.length > 0;
25
+ }
21
26
  const dispatch = createEventDispatcher();
22
27
  const raiseClose = (value2) => {
23
28
  dispatch("close", { value: value2 });
@@ -28,10 +33,23 @@ const raiseOpen = (value2) => {
28
33
  const raiseSelect = (value2) => {
29
34
  dispatch("select", { value: value2 });
30
35
  };
31
- const onClick = () => {
32
- open = !open;
33
- if (open) {
34
- setTimeout(() => focusFirstChild($children), 10);
36
+ export const click = () => {
37
+ buttonRef?.click();
38
+ };
39
+ export const blur = () => {
40
+ buttonRef?.blur();
41
+ };
42
+ export const focus = (options) => {
43
+ buttonRef?.focus(options);
44
+ };
45
+ const onClick = async () => {
46
+ if (!open) {
47
+ openValues.set(["menu-button"]);
48
+ await tick();
49
+ menuRef?.focusFirstMenuItem();
50
+ } else {
51
+ open = false;
52
+ openValues.set([]);
35
53
  }
36
54
  };
37
55
  $: {
@@ -40,29 +58,29 @@ $: {
40
58
  }
41
59
  prevOpen = open;
42
60
  }
43
- export const click = () => {
44
- buttonRef?.click();
45
- };
46
- export const blur = () => {
47
- buttonRef?.blur();
61
+ const closeAllMenus = () => {
62
+ openValues.set([]);
48
63
  };
49
- export const focus = (options) => {
50
- buttonRef?.focus(options);
64
+ const onClickOutside = (event) => {
65
+ const {
66
+ detail: { mouseEvent }
67
+ } = event;
68
+ let element = mouseEvent.target;
69
+ while (element) {
70
+ if (element.getAttribute("data-root-value") === value) {
71
+ return;
72
+ }
73
+ element = element.parentElement;
74
+ }
75
+ closeAllMenus?.();
51
76
  };
52
77
  setContext(MENU_ITEM_CONTEXT_KEY, {
53
- rootValue: value,
54
78
  depth: 1,
55
- register: (menuItem) => {
56
- children.set([...$children, menuItem]);
57
- },
58
- unregister: (menuItem) => {
59
- children.set($children.filter((x) => x.value !== menuItem.value));
60
- },
61
- closeMenu: (recursive) => {
79
+ openValues,
80
+ rootValue: value,
81
+ closeContainingMenu: () => {
62
82
  open = false;
63
83
  },
64
- focusPrevious: (currentValue) => focusPreviousChild($children, currentValue),
65
- focusNext: (currentValue) => focusNextChild($children, currentValue),
66
84
  onOpen: raiseOpen,
67
85
  onClose: raiseClose,
68
86
  onSelect: raiseSelect
@@ -117,12 +135,14 @@ setContext(MENU_ITEM_CONTEXT_KEY, {
117
135
  on:wheel
118
136
  {...$$restProps}
119
137
  >
120
- <div class="reference" bind:this={reference}>
138
+ <div class="reference" bind:this={reference} use:clickOutside on:click_outside={onClickOutside}>
121
139
  <slot {shape} {variant} />
122
140
  </div>
123
- <Menu id={menuId} {reference} {open}>
124
- <slot name="items" />
125
- </Menu>
141
+ <Popover {reference} placement="bottom-start" {open}>
142
+ <Menu bind:this={menuRef} id={menuId} {reference} {open}>
143
+ <slot name="items" />
144
+ </Menu>
145
+ </Popover>
126
146
  </Button>
127
147
 
128
148
  <style>
@@ -1 +1,2 @@
1
1
  export declare const MENU_ITEM_CONTEXT_KEY = "sterlingMenuItem";
2
+ export declare const MENU_ITEM_ROLES: readonly ["menuitem", "menuitemcheckbox", "menuitemradio"];
@@ -1 +1,2 @@
1
1
  export const MENU_ITEM_CONTEXT_KEY = 'sterlingMenuItem';
2
+ export const MENU_ITEM_ROLES = ['menuitem', 'menuitemcheckbox', 'menuitemradio'];