@oscarpalmer/oui 0.27.0 → 0.29.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 (54) hide show
  1. package/dist/css/floatable.css +9 -0
  2. package/dist/js/dialog.d.mts +3 -1
  3. package/dist/js/dialog.mjs +35 -19
  4. package/dist/js/{draggable-zszaW1qS.d.mts → draggable-fIYXd8eO.d.mts} +3 -3
  5. package/dist/js/floatable/floatable.embedded.d.mts +2 -2
  6. package/dist/js/floatable/floatable.embedded.mjs +17 -16
  7. package/dist/js/floatable/floatable.standalone.d.mts +1 -1
  8. package/dist/js/{floatable.embedded-YxGwzgbE.d.mts → floatable.embedded-DUR8bQBK.d.mts} +1 -2
  9. package/dist/js/focus-trap/focus-trap.embedded.mjs +6 -5
  10. package/dist/js/index.d.mts +4 -3
  11. package/dist/js/index.mjs +6 -2
  12. package/dist/js/internal/attributable.mjs +3 -4
  13. package/dist/js/internal/draggable.d.mts +1 -1
  14. package/dist/js/internal/draggable.mjs +12 -9
  15. package/dist/js/internal/navigable.d.mts +32 -0
  16. package/dist/js/internal/navigable.mjs +107 -0
  17. package/dist/js/menu.d.mts +1 -0
  18. package/dist/js/menu.mjs +168 -0
  19. package/dist/js/movable/movable.embedded.d.mts +1 -1
  20. package/dist/js/movable/movable.embedded.mjs +8 -13
  21. package/dist/js/movable/movable.standalone.d.mts +1 -1
  22. package/dist/js/popover.d.mts +1 -1
  23. package/dist/js/popover.mjs +27 -12
  24. package/dist/js/resizable/resizable.embedded.d.mts +2 -0
  25. package/dist/js/resizable/resizable.embedded.mjs +191 -0
  26. package/dist/js/resizable/resizable.standalone.d.mts +2 -0
  27. package/dist/js/resizable/resizable.standalone.mjs +2 -0
  28. package/dist/js/resizable.embedded-CXrUVPzR.d.mts +35 -0
  29. package/dist/js/sortable/sortable.embedded.d.mts +1 -1
  30. package/dist/js/sortable/sortable.embedded.mjs +15 -13
  31. package/dist/js/sortable/sortable.standalone.d.mts +1 -1
  32. package/dist/js/splitter.mjs +30 -26
  33. package/dist/js/toolbar.mjs +12 -88
  34. package/dist/js/tooltip.d.mts +1 -1
  35. package/dist/js/tooltip.mjs +12 -17
  36. package/package.json +11 -3
  37. package/src/css/floatable.scss +9 -0
  38. package/src/css/form.scss +6 -6
  39. package/src/js/dialog.ts +50 -24
  40. package/src/js/floatable/floatable.embedded.ts +19 -18
  41. package/src/js/focus-trap/focus-trap.embedded.ts +6 -5
  42. package/src/js/index.ts +3 -1
  43. package/src/js/internal/attributable.ts +3 -5
  44. package/src/js/internal/draggable.ts +13 -10
  45. package/src/js/internal/navigable.ts +247 -0
  46. package/src/js/menu.ts +345 -0
  47. package/src/js/movable/movable.embedded.ts +10 -13
  48. package/src/js/popover.ts +41 -15
  49. package/src/js/resizable/resizable.embedded.ts +390 -0
  50. package/src/js/resizable/resizable.standalone.ts +5 -0
  51. package/src/js/sortable/sortable.embedded.ts +17 -22
  52. package/src/js/splitter.ts +41 -36
  53. package/src/js/toolbar.ts +27 -178
  54. package/src/js/tooltip.ts +18 -30
package/src/js/toolbar.ts CHANGED
@@ -1,153 +1,41 @@
1
- import {clamp} from '@oscarpalmer/atoms/number';
2
- import {on} from '@oscarpalmer/toretto/event';
3
- import {findAncestor} from '@oscarpalmer/toretto/find';
4
- import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
5
- import type {RemovableEventListener} from '@oscarpalmer/toretto/models';
6
1
  import {attributable} from './internal/attributable';
2
+ import {getNavigable, OuiNavigable, updateNavigableFocus} from './internal/navigable';
3
+
4
+ // #region Types
7
5
 
8
6
  class OuiToolbar {
9
- readonly #state: OuiToolbarState;
7
+ #navigable: OuiNavigable;
10
8
 
11
- constructor(state: OuiToolbarState) {
12
- this.#state = state;
9
+ constructor(navigable: OuiNavigable) {
10
+ this.#navigable = navigable;
13
11
 
14
- updateFocus(this.#state, false);
12
+ updateNavigableFocus(this.#navigable, false);
15
13
  }
16
14
 
17
15
  destroy(): void {
18
- for (const listener of this.#state.listeners) {
19
- listener();
20
- }
16
+ this.#navigable.destroy();
21
17
 
22
- this.#state.active = undefined as never;
23
- this.#state.element = undefined as never;
24
- this.#state.listeners = undefined as never;
18
+ this.#navigable = undefined as never;
25
19
  }
26
20
  }
27
21
 
28
- type OuiToolbarOrientation = 'horizontal' | 'vertical';
29
-
30
- type OuiToolbarState = {
31
- active: OuiToolbarStateActive;
32
- element: HTMLElement;
33
- listeners: RemovableEventListener[];
34
- orientation: OuiToolbarOrientation;
35
- };
22
+ // #endregion
36
23
 
37
- type OuiToolbarStateActive = {
38
- element?: HTMLElement;
39
- index: number;
40
- };
24
+ // #region Functions
41
25
 
42
26
  function addToolbar(element: HTMLElement): void {
43
27
  if (!instances.has(element)) {
44
- instances.set(element, new OuiToolbar(getState(element)));
45
- }
46
- }
47
-
48
- function getItems(element: HTMLElement): HTMLElement[] {
49
- return [...element.querySelectorAll<HTMLElement>(SELECTOR)].filter(isValidItem);
50
- }
51
-
52
- function getOrientation(element: HTMLElement): OuiToolbarOrientation {
53
- const attribute = element.getAttribute(ARIA_ORIENTATION);
54
-
55
- return ORIENTATIONS.has(attribute as OuiToolbarOrientation)
56
- ? (attribute as OuiToolbarOrientation)
57
- : ORIENTATION_HORIZONTAL;
58
- }
59
-
60
- function getState(element: HTMLElement): OuiToolbarState {
61
- const state: OuiToolbarState = {
62
- element,
63
- active: {
64
- index: 0,
65
- },
66
- listeners: [
67
- on(element, EVENT_FOCUSIN, event => {
68
- onFocusin(state, event);
69
- }),
70
- on(
71
- element,
72
- EVENT_KEYDOWN,
73
- event => {
74
- onKeydown(state, event);
75
- },
76
- {
77
- passive: false,
78
- },
28
+ instances.set(
29
+ element,
30
+ new OuiToolbar(
31
+ getNavigable(element, {
32
+ role: 'toolbar',
33
+ selector: SELECTOR,
34
+ vertical: false,
35
+ }),
79
36
  ),
80
- ],
81
- orientation: getOrientation(element),
82
- };
83
-
84
- return state;
85
- }
86
-
87
- function isValidItem(element: HTMLElement): boolean {
88
- return PROPERTY_DISABLED in element ? element.disabled !== true : true;
89
- }
90
-
91
- function onKeydown(state: OuiToolbarState, event: KeyboardEvent): void {
92
- if (!KEYS_ALL.has(event.key)) {
93
- return;
94
- }
95
-
96
- const items = getItems(state.element);
97
-
98
- if (items.length === 0) {
99
- return;
100
- }
101
-
102
- let index: number | undefined;
103
-
104
- if (KEYS_ABSOLUTE.has(event.key)) {
105
- index = event.key === KEY_HOME ? 0 : items.length - 1;
106
- } else if (
107
- state.orientation === ORIENTATION_HORIZONTAL
108
- ? KEYS_HORIZONTAL.has(event.key)
109
- : KEYS_VERTICAL.has(event.key)
110
- ) {
111
- if (state.active.element instanceof HTMLSelectElement) {
112
- event.preventDefault();
113
- }
114
-
115
- index = clamp(
116
- state.active.index + (KEYS_NEXT.has(event.key) ? 1 : -1),
117
- 0,
118
- items.length - 1,
119
- true,
120
37
  );
121
38
  }
122
-
123
- if (index == null || index === state.active.index) {
124
- return;
125
- }
126
-
127
- state.active.element = items[index];
128
- state.active.index = index;
129
-
130
- updateFocus(state, true, items);
131
- }
132
-
133
- function onFocusin(state: OuiToolbarState, event: FocusEvent): void {
134
- const item = findAncestor(event, SELECTOR);
135
-
136
- if (!isHTMLOrSVGElement(item) || !isValidItem(item)) {
137
- return;
138
- }
139
-
140
- const items = getItems(state.element);
141
- const index = items.indexOf(item);
142
-
143
- if (index === -1) {
144
- return;
145
- }
146
-
147
- state.active.element = item;
148
- state.active.index = index;
149
-
150
- updateFocus(state, false, items);
151
39
  }
152
40
 
153
41
  function removeToolbar(element: HTMLElement): void {
@@ -156,59 +44,20 @@ function removeToolbar(element: HTMLElement): void {
156
44
  instances.delete(element);
157
45
  }
158
46
 
159
- function updateFocus(state: OuiToolbarState, focus: boolean, items?: HTMLElement[]): void {
160
- const updated = items ?? getItems(state.element);
161
- const {length} = updated;
162
-
163
- if (length === 0) {
164
- return;
165
- }
166
-
167
- for (let index = 0; index < length; index += 1) {
168
- updated[index].tabIndex = index === state.active.index ? 0 : -1;
169
- }
47
+ // #endregion
170
48
 
171
- state.active.element ??= updated[state.active.index];
172
-
173
- if (focus) {
174
- state.active.element?.focus();
175
- }
176
- }
177
-
178
- const ARIA_ORIENTATION = 'aria-orientation';
49
+ // #region Variables
179
50
 
180
51
  const ATTRIBUTE = 'oui-toolbar';
181
52
 
182
- const EVENT_KEYDOWN = 'keydown';
183
-
184
- const EVENT_FOCUSIN = 'focusin';
185
-
186
- const KEY_ARROW_DOWN = 'ArrowDown';
187
-
188
- const KEY_ARROW_RIGHT = 'ArrowRight';
189
-
190
- const KEY_END = 'End';
191
-
192
- const KEY_HOME = 'Home';
193
-
194
- const KEYS_ABSOLUTE = new Set([KEY_END, KEY_HOME]);
195
-
196
- const KEYS_HORIZONTAL = new Set([KEY_ARROW_RIGHT, 'ArrowLeft']);
197
-
198
- const KEYS_VERTICAL = new Set([KEY_ARROW_DOWN, 'ArrowUp']);
199
-
200
- const KEYS_ALL = new Set([...KEYS_ABSOLUTE, ...KEYS_HORIZONTAL, ...KEYS_VERTICAL]);
201
-
202
- const KEYS_NEXT = new Set([KEY_ARROW_DOWN, KEY_ARROW_RIGHT]);
203
-
204
- const ORIENTATION_HORIZONTAL: OuiToolbarOrientation = 'horizontal';
205
-
206
- const ORIENTATIONS = new Set<OuiToolbarOrientation>([ORIENTATION_HORIZONTAL, 'vertical']);
207
-
208
- const PROPERTY_DISABLED = 'disabled';
209
-
210
53
  const SELECTOR = `[${ATTRIBUTE}-item]`;
211
54
 
212
55
  const instances = new WeakMap<HTMLElement, OuiToolbar>();
213
56
 
57
+ // #endregion
58
+
59
+ // #region Initialization
60
+
214
61
  attributable(ATTRIBUTE, addToolbar, removeToolbar);
62
+
63
+ // #endregion
package/src/js/tooltip.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import {isNullableOrWhitespace} from '@oscarpalmer/atoms/is';
2
+ import {getAria} from '@oscarpalmer/toretto/aria';
3
+ import {setAttribute} from '@oscarpalmer/toretto/attribute';
2
4
  import {createElement} from '@oscarpalmer/toretto/create';
3
5
  import {on} from '@oscarpalmer/toretto/event';
4
6
  import {findAncestor} from '@oscarpalmer/toretto/find';
5
- import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
6
7
  import {
7
8
  createEmbeddedFloatable,
8
- FLOATABLE_STATE_OPEN,
9
9
  type OuiFloatable,
10
10
  type OuiFloatableOptions,
11
11
  } from './floatable/floatable.embedded';
@@ -154,7 +154,11 @@ function destroyTooltip(state: OuiTooltipState): void {
154
154
  state.tooltip = undefined as never;
155
155
  }
156
156
 
157
- function findElements(attribute: string | null): HTMLElement[] {
157
+ function findElements(attribute?: string): HTMLElement[] | undefined {
158
+ if (attribute == null) {
159
+ return;
160
+ }
161
+
158
162
  const ids = attribute?.split(EXPRESSION_WHITESPACE) ?? [];
159
163
  const elements: HTMLElement[] = [];
160
164
 
@@ -165,7 +169,7 @@ function findElements(attribute: string | null): HTMLElement[] {
165
169
 
166
170
  const element = document.querySelector(`#${id}`);
167
171
 
168
- if (isHTMLOrSVGElement(element)) {
172
+ if (element != null) {
169
173
  const cloned = element.cloneNode(true) as HTMLElement;
170
174
 
171
175
  cloned.hidden = false;
@@ -178,34 +182,32 @@ function findElements(attribute: string | null): HTMLElement[] {
178
182
  }
179
183
 
180
184
  function getContent(): HTMLElement {
181
- const content = createElement(CONTENT_TAGNAME, {
185
+ const content = createElement('div', {
182
186
  popover: 'hint',
183
187
  role: 'tooltip',
184
188
  tabIndex: -1,
185
189
  });
186
190
 
187
- content.setAttribute(ATTRIBUTE_CONTENT, '');
191
+ setAttribute(content, ATTRIBUTE_CONTENT, '');
188
192
 
189
193
  return content;
190
194
  }
191
195
 
192
196
  function getElements(anchor: HTMLElement): HTMLElement[] | undefined {
193
- let elements = findElements(anchor.getAttribute(ARIA_LABELLEDBY));
197
+ let elements = findElements(getAria(anchor, 'labelledby'));
194
198
 
195
- if (elements.length > 0) {
196
- return elements;
199
+ if (elements == null || elements.length === 0) {
200
+ elements = findElements(getAria(anchor, 'describedby'));
197
201
  }
198
202
 
199
- elements = findElements(anchor.getAttribute(ARIA_DESCRIBEDBY));
200
-
201
- if (elements.length > 0) {
202
- return elements;
203
+ if (elements != null && elements.length > 0) {
204
+ return elements as HTMLElement[];
203
205
  }
204
206
 
205
- let text = anchor.getAttribute(ARIA_LABEL);
207
+ let text = getAria(anchor, 'label');
206
208
 
207
209
  if (isNullableOrWhitespace(text)) {
208
- text = anchor.getAttribute(ARIA_DESCRIPTION);
210
+ text = getAria(anchor, 'description');
209
211
  }
210
212
 
211
213
  if (isNullableOrWhitespace(text)) {
@@ -276,11 +278,7 @@ function onPointermove(event: Event): void {
276
278
  }
277
279
 
278
280
  function onToggle(event: ToggleEvent): void {
279
- if (event.newState === FLOATABLE_STATE_OPEN) {
280
- content.setAttribute(ATTRIBUTE_CONTENT_OPEN, '');
281
- } else {
282
- content.removeAttribute(ATTRIBUTE_CONTENT_OPEN);
283
- }
281
+ setAttribute(content, ATTRIBUTE_CONTENT_OPEN, event.newState === 'open' ? '' : undefined);
284
282
  }
285
283
 
286
284
  function removeTooltip(element: HTMLElement): void {
@@ -311,16 +309,6 @@ function setTooltipDelay(value: number): void {
311
309
 
312
310
  // #region Variables
313
311
 
314
- const ARIA_DESCRIBEDBY = 'aria-describedby';
315
-
316
- const ARIA_DESCRIPTION = 'aria-description';
317
-
318
- const ARIA_LABEL = 'aria-label';
319
-
320
- const ARIA_LABELLEDBY = 'aria-labelledby';
321
-
322
- const CONTENT_TAGNAME = 'div';
323
-
324
312
  const EXPRESSION_WHITESPACE = /\s+/;
325
313
 
326
314
  const ATTRIBUTE = 'oui-tooltip';