@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
@@ -0,0 +1,390 @@
1
+ import {isPlainObject} from '@oscarpalmer/atoms/is';
2
+ import type {EventPosition} from '@oscarpalmer/atoms/models';
3
+ import {getNumber} from '@oscarpalmer/atoms/number';
4
+ import {getAttribute, setAttribute} from '@oscarpalmer/toretto/attribute';
5
+ import {getPosition, on} from '@oscarpalmer/toretto/event';
6
+ import {findAncestor} from '@oscarpalmer/toretto/find';
7
+ import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
8
+ import {setStyles, toggleStyles, type StyleToggler} from '@oscarpalmer/toretto/style';
9
+ import supportsTouch from '@oscarpalmer/toretto/touch';
10
+ import {attributable} from '../internal/attributable';
11
+
12
+ // #region Types
13
+
14
+ export type CreateOuiResizableOptions = {
15
+ maximumHeight?: number;
16
+ maximumWidth?: number;
17
+ minimumHeight?: number;
18
+ minimumWidth?: number;
19
+ };
20
+
21
+ export class OuiResizable {
22
+ readonly #state: OuiResizableState;
23
+
24
+ constructor(state: OuiResizableState) {
25
+ this.#state = state;
26
+ }
27
+
28
+ destroy(): void {
29
+ destroyResizable(this.#state);
30
+ }
31
+
32
+ set(): void {
33
+ const [width, height] = getAndSetDimensions(this.#state.element, this.#state.options);
34
+
35
+ console.log('set', width, height);
36
+
37
+ this.#state.width = width;
38
+ this.#state.height = height;
39
+ }
40
+
41
+ reset(): void {
42
+ setStyles(this.#state.element, {
43
+ height: '',
44
+ width: '',
45
+ });
46
+ }
47
+ }
48
+
49
+ type OuiResizableItem = {
50
+ instance: OuiResizable;
51
+ state: OuiResizableState;
52
+ };
53
+
54
+ type OuiResizableState = {
55
+ element: HTMLElement;
56
+ handle: HTMLElement;
57
+ height: number;
58
+ options: Required<CreateOuiResizableOptions>;
59
+ styles: StyleToggler;
60
+ width: number;
61
+ };
62
+
63
+ // #endregion
64
+
65
+ // #region Functions
66
+
67
+ function addResizable(
68
+ element: HTMLElement,
69
+ options?: CreateOuiResizableOptions,
70
+ ): OuiResizable | undefined {
71
+ let item = items.get(element);
72
+
73
+ if (item == null) {
74
+ const handle = element.querySelector(SELECTOR_HANDLE_SCOPED);
75
+
76
+ if (!isHTMLOrSVGElement(handle)) {
77
+ return;
78
+ }
79
+
80
+ const state = getState(element, handle, options);
81
+ const instance = new OuiResizable(state);
82
+
83
+ item = {
84
+ instance,
85
+ state,
86
+ };
87
+
88
+ items.set(element, item);
89
+ }
90
+
91
+ return item?.instance;
92
+ }
93
+
94
+ /**
95
+ * Creates _(or retrieves)_ a _OuiResizable_ instance for an element
96
+ *
97
+ * @param element Element to make resizable
98
+ * @returns _OuiResizable_ instance
99
+ */
100
+ export function createResizable(
101
+ element: HTMLElement,
102
+ options?: CreateOuiResizableOptions,
103
+ ): OuiResizable {
104
+ if (!isHTMLOrSVGElement(element)) {
105
+ throw new TypeError(MESSAGE_ELEMENT);
106
+ }
107
+
108
+ if (!isHTMLOrSVGElement(element.querySelector(SELECTOR_HANDLE_SCOPED))) {
109
+ throw new Error(MESSAGE_HANDLE);
110
+ }
111
+
112
+ return addResizable(element, options ?? {})!;
113
+ }
114
+
115
+ function destroyResizable(state: OuiResizableState): void {
116
+ state.element = undefined as never;
117
+ state.handle = undefined as never;
118
+ }
119
+
120
+ function getAndSetDimensions(
121
+ element: HTMLElement,
122
+ options: Required<CreateOuiResizableOptions>,
123
+ input?: Pick<DOMRect, 'height' | 'width'>,
124
+ ): [number, number] {
125
+ let {height, width} = input ?? element.getBoundingClientRect();
126
+
127
+ if (options.maximumWidth > -1 && width > options.maximumWidth) {
128
+ width = options.maximumWidth;
129
+ } else if (options.minimumWidth > -1 && width < options.minimumWidth) {
130
+ width = options.minimumWidth;
131
+ }
132
+
133
+ if (options.maximumHeight > -1 && height > options.maximumHeight) {
134
+ height = options.maximumHeight;
135
+ } else if (options.minimumHeight > -1 && height < options.minimumHeight) {
136
+ height = options.minimumHeight;
137
+ }
138
+
139
+ if (element.checkVisibility()) {
140
+ setStyles(element, {
141
+ height: `${height}px`,
142
+ width: `${width}px`,
143
+ });
144
+ }
145
+
146
+ return [width, height];
147
+ }
148
+
149
+ function getProperties(
150
+ element: HTMLElement,
151
+ widthProperty: 'maxWidth' | 'minWidth',
152
+ heightProperty: 'maxHeight' | 'minHeight',
153
+ ): [string, string] {
154
+ const computed = getComputedStyle(element);
155
+
156
+ return [computed[widthProperty], computed[heightProperty]];
157
+ }
158
+
159
+ function getRestrictions(
160
+ maximum: string | [unknown, unknown],
161
+ minimum: string | [unknown, unknown],
162
+ ): Required<CreateOuiResizableOptions> {
163
+ const [maximumWidth, maximumHeight] = getXAndY.apply(
164
+ null,
165
+ typeof maximum === 'string' ? [maximum] : maximum,
166
+ );
167
+
168
+ const [minimumWidth, minimumHeight] = getXAndY.apply(
169
+ null,
170
+ typeof minimum === 'string' ? [minimum] : minimum,
171
+ );
172
+
173
+ if (maximumWidth > -1 && minimumWidth > -1 && maximumWidth <= minimumWidth) {
174
+ throw new Error(MESSAGE_WIDTH);
175
+ }
176
+
177
+ if (maximumHeight > -1 && minimumHeight > -1 && maximumHeight <= minimumHeight) {
178
+ throw new Error(MESSAGE_HEIGHT);
179
+ }
180
+
181
+ return {
182
+ maximumHeight,
183
+ maximumWidth,
184
+ minimumHeight,
185
+ minimumWidth,
186
+ };
187
+ }
188
+
189
+ function getState(
190
+ element: HTMLElement,
191
+ handle: HTMLElement,
192
+ input?: CreateOuiResizableOptions,
193
+ ): OuiResizableState {
194
+ setAttribute(element, RESIZABLE_ATTRIBUTE, '');
195
+
196
+ const options = getValidOptions(element, input);
197
+
198
+ const [width, height] = getAndSetDimensions(element, options);
199
+
200
+ return {
201
+ element,
202
+ handle,
203
+ height,
204
+ options,
205
+ width,
206
+ styles: toggleStyles(element, styles),
207
+ };
208
+ }
209
+
210
+ function getValidOptions(
211
+ element: HTMLElement,
212
+ input?: CreateOuiResizableOptions,
213
+ ): Required<CreateOuiResizableOptions> {
214
+ const original = isPlainObject(input) ? input : {};
215
+
216
+ return {
217
+ ...getRestrictions(
218
+ input == null
219
+ ? (getAttribute(element, ATTRIBUTE_MAXIMUM) ??
220
+ getProperties(element, 'maxWidth', 'maxHeight'))
221
+ : [original.maximumWidth, original.maximumHeight],
222
+ input == null
223
+ ? (getAttribute(element, ATTRIBUTE_MINIMUM) ??
224
+ getProperties(element, 'minWidth', 'minHeight'))
225
+ : [original.minimumWidth, original.minimumHeight],
226
+ ),
227
+ };
228
+ }
229
+
230
+ function getXAndY(first: unknown, second?: unknown): [number, number] {
231
+ const [x, y] = typeof first === 'string' && second == null ? first.split(',') : [first, second];
232
+
233
+ let xAsNumber = typeof x === 'string' ? Number.parseFloat(x) : getNumber(x);
234
+ let yAsNumber = typeof y === 'string' ? Number.parseFloat(y) : getNumber(y);
235
+
236
+ if (Number.isNaN(xAsNumber) || xAsNumber <= 0) {
237
+ xAsNumber = -1;
238
+ }
239
+
240
+ if (Number.isNaN(yAsNumber) || yAsNumber <= 0) {
241
+ yAsNumber = -1;
242
+ }
243
+
244
+ return [xAsNumber, yAsNumber];
245
+ }
246
+
247
+ function onKeydown(event: KeyboardEvent): void {
248
+ if (current == null || event.key !== 'Escape') {
249
+ return;
250
+ }
251
+
252
+ setStyles(current.state.element, {
253
+ height: `${current.state.height}px`,
254
+ width: `${current.state.width}px`,
255
+ });
256
+
257
+ resetResizable();
258
+ }
259
+
260
+ function onPointerdown(event: MouseEvent | TouchEvent): void {
261
+ if (
262
+ event.altKey ||
263
+ event.ctrlKey ||
264
+ event.metaKey ||
265
+ event.shiftKey ||
266
+ (supportsTouch.value
267
+ ? event.type === 'mousedown'
268
+ : event.type === 'touchstart' || (event as MouseEvent).button !== 0)
269
+ ) {
270
+ return;
271
+ }
272
+
273
+ const handle = findAncestor(event, SELECTOR_HANDLE);
274
+ const element = handle && findAncestor(handle, SELECTOR);
275
+ const stored = element && items.get(element as HTMLElement);
276
+ const position = getPosition(event);
277
+
278
+ if (stored == null || position == null) {
279
+ return;
280
+ }
281
+
282
+ if (supportsTouch.value) {
283
+ event.preventDefault();
284
+ }
285
+
286
+ current = stored;
287
+ start = position;
288
+
289
+ current.state.styles.set();
290
+ styleToggler.set();
291
+ }
292
+
293
+ function onPointermove(event: MouseEvent | TouchEvent): void {
294
+ if (current == null) {
295
+ return;
296
+ }
297
+
298
+ const {x, y} = getPosition(event) ?? {};
299
+
300
+ if (x == null || y == null) {
301
+ return;
302
+ }
303
+
304
+ const width = current.state.width + (x - (start?.x ?? 0));
305
+ const height = current.state.height + (y - (start?.y ?? 0));
306
+
307
+ getAndSetDimensions(current.state.element, current.state.options, {height, width});
308
+ }
309
+
310
+ function onPointerup(): void {
311
+ if (current == null) {
312
+ return;
313
+ }
314
+
315
+ const {height, width} = current.state.element.getBoundingClientRect();
316
+
317
+ current.state.height = height;
318
+ current.state.width = width;
319
+
320
+ resetResizable();
321
+ }
322
+
323
+ function removeResizable(element: HTMLElement): void {
324
+ items.get(element)?.instance.destroy();
325
+
326
+ items.delete(element);
327
+ }
328
+
329
+ function resetResizable(): void {
330
+ current?.state.styles.remove();
331
+ styleToggler.remove();
332
+
333
+ current = undefined;
334
+ start = undefined;
335
+ }
336
+
337
+ // #endregion
338
+
339
+ // #region Variables
340
+
341
+ export const RESIZABLE_ATTRIBUTE = 'oui-resizable';
342
+
343
+ export const RESIZABLE_ATTRIBUTE_HANDLE = `${RESIZABLE_ATTRIBUTE}-handle`;
344
+
345
+ const ATTRIBUTE_MAXIMUM = `${RESIZABLE_ATTRIBUTE}-maximum`;
346
+
347
+ const ATTRIBUTE_MINIMUM = `${RESIZABLE_ATTRIBUTE}-minimum`;
348
+
349
+ const MESSAGE_ELEMENT = 'The element must be an instance of HTMLElement or SVGElement';
350
+
351
+ const MESSAGE_HEIGHT = 'The maximum height must be greater than the minimum height';
352
+
353
+ const MESSAGE_HANDLE = `The element must contain a child with the attribute "${RESIZABLE_ATTRIBUTE_HANDLE}"`;
354
+
355
+ const MESSAGE_WIDTH = 'The maximum width must be greater than the minimum width';
356
+
357
+ const SELECTOR = `[${RESIZABLE_ATTRIBUTE}]`;
358
+
359
+ const SELECTOR_HANDLE = `[${RESIZABLE_ATTRIBUTE_HANDLE}]`;
360
+
361
+ const SELECTOR_HANDLE_SCOPED = `:scope > ${SELECTOR_HANDLE}`;
362
+
363
+ const items = new WeakMap<HTMLElement, OuiResizableItem>();
364
+
365
+ const styles = {
366
+ touchAction: 'none',
367
+ userSelect: 'none',
368
+ webkitUserSelect: 'none',
369
+ };
370
+
371
+ const styleToggler = toggleStyles(document.body, styles);
372
+
373
+ let current: OuiResizableItem | undefined;
374
+
375
+ let start: EventPosition | undefined;
376
+
377
+ // #endregion
378
+
379
+ // #region Initialization
380
+
381
+ on(document, 'keydown', onKeydown);
382
+ on(document, 'mousedown', onPointerdown);
383
+ on(document, 'pointermove', onPointermove);
384
+ on(document, 'pointerup', onPointerup);
385
+ on(document, 'touchcancel', onPointerup);
386
+ on(document, 'touchstart', onPointerdown, {passive: false});
387
+
388
+ attributable(RESIZABLE_ATTRIBUTE, addResizable, removeResizable);
389
+
390
+ // #endregion
@@ -0,0 +1,5 @@
1
+ export {
2
+ createResizable as createOuiResizable,
3
+ type CreateOuiResizableOptions,
4
+ type OuiResizable,
5
+ } from './resizable.embedded';
@@ -1,8 +1,10 @@
1
1
  import type {EventPosition, PlainObject} from '@oscarpalmer/atoms/models';
2
+ import {getAttribute, setAttribute, setAttributes} from '@oscarpalmer/toretto/attribute';
2
3
  import {createElement} from '@oscarpalmer/toretto/create';
3
4
  import {getPosition} from '@oscarpalmer/toretto/event';
4
5
  import {findAncestor, getElementFromPosition} from '@oscarpalmer/toretto/find';
5
6
  import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
7
+ import {setStyle} from '@oscarpalmer/toretto/style';
6
8
  import {attributable} from '../internal/attributable';
7
9
  import {
8
10
  addDraggable,
@@ -50,7 +52,7 @@ export class OuiSortable extends OuiDraggable<OuiSortableState> {
50
52
  (getDraggableStates(element)?.sortable as OuiDraggableState & OuiSortableState)?.origin,
51
53
  },
52
54
  {
53
- connections: element.getAttribute(ATTRIBUTE_CONNECTIONS),
55
+ connections: getAttribute(element, ATTRIBUTE_CONNECTIONS),
54
56
  },
55
57
  );
56
58
  }
@@ -67,7 +69,7 @@ type OuiSortablePlaceholderDrag = {
67
69
  };
68
70
 
69
71
  type OuiSortableState = {
70
- connections: string | null;
72
+ connections: string | null | undefined;
71
73
  origin?: HTMLElement;
72
74
  };
73
75
 
@@ -166,10 +168,11 @@ function getOriginalPosition(
166
168
  function getPositionPlaceholder(item: OuiDraggableItem<OuiSortableState>): HTMLElement {
167
169
  placeholder.position ??= createElement('div');
168
170
 
169
- placeholder.position.setAttribute(ATTRIBUTE_PLACEHOLDER_POSITION, '');
171
+ setAttribute(placeholder.position, ATTRIBUTE_PLACEHOLDER_POSITION, '');
170
172
 
171
173
  if (item.state.origin != null) {
172
- placeholder.position.style.setProperty(
174
+ setStyle(
175
+ placeholder.position,
173
176
  '--placeholder-height',
174
177
  `${item.state.origin.getBoundingClientRect().height}px`,
175
178
  );
@@ -188,9 +191,9 @@ function onBegin(
188
191
 
189
192
  setPlaceholder(globals, item, element);
190
193
 
191
- element.setAttribute(ATTRIBUTE_ORIGIN, '');
194
+ setAttribute(element, ATTRIBUTE_ORIGIN, '');
192
195
 
193
- document.body.setAttribute(ATTRIBUTE_ACTIVE, '');
196
+ setAttribute(document.body, ATTRIBUTE_ACTIVE, '');
194
197
  }
195
198
 
196
199
  function onCancel(_: OuiDraggableGlobals, item: OuiDraggableItem<OuiSortableState>): void {
@@ -218,7 +221,7 @@ function onEnd(
218
221
  let element = findAncestor(target, SELECTOR_ITEM) as HTMLElement;
219
222
 
220
223
  if (element?.hasAttribute(ATTRIBUTE_PLACEHOLDER_POSITION)) {
221
- const position = element.getAttribute(ATTRIBUTE_PLACEHOLDER_POSITION);
224
+ const position = getAttribute(element, ATTRIBUTE_PLACEHOLDER_POSITION);
222
225
 
223
226
  if (position === 'after') {
224
227
  element = element.previousElementSibling as HTMLElement;
@@ -344,7 +347,7 @@ function onMove(
344
347
  toElement.insertAdjacentElement(INSERT_AFTER, placeholder);
345
348
  }
346
349
 
347
- placeholder.setAttribute(ATTRIBUTE_PLACEHOLDER_POSITION, place);
350
+ setAttribute(placeholder, ATTRIBUTE_PLACEHOLDER_POSITION, place);
348
351
  } else {
349
352
  placeholder.remove();
350
353
  }
@@ -417,9 +420,8 @@ function setPlaceholder(
417
420
  }
418
421
 
419
422
  function updatePlaceholder(item: OuiDraggableItem<OuiSortableState>, element: HTMLElement): void {
420
- element.style.position = 'fixed';
421
-
422
- element.setAttribute(ATTRIBUTE_PLACEHOLDER_DRAG, '');
423
+ setAttribute(element, ATTRIBUTE_PLACEHOLDER_DRAG, '');
424
+ setStyle(element, 'position', 'fixed');
423
425
 
424
426
  const state = getDraggableStates(item.state.element)?.sortable;
425
427
 
@@ -427,17 +429,10 @@ function updatePlaceholder(item: OuiDraggableItem<OuiSortableState>, element: HT
427
429
  return;
428
430
  }
429
431
 
430
- if (state.horizontal) {
431
- element.setAttribute(ATTRIBUTE_HORIZONTAL, '');
432
- } else {
433
- element.removeAttribute(ATTRIBUTE_HORIZONTAL);
434
- }
435
-
436
- if (state.vertical) {
437
- element.setAttribute(ATTRIBUTE_VERTICAL, '');
438
- } else {
439
- element.removeAttribute(ATTRIBUTE_VERTICAL);
440
- }
432
+ setAttributes(element, {
433
+ [ATTRIBUTE_HORIZONTAL]: state.horizontal ? '' : undefined,
434
+ [ATTRIBUTE_VERTICAL]: state.vertical ? '' : undefined,
435
+ });
441
436
  }
442
437
 
443
438
  function validatePosition(
@@ -1,9 +1,11 @@
1
1
  import {isNullableOrWhitespace} from '@oscarpalmer/atoms/is';
2
2
  import {round} from '@oscarpalmer/atoms/math';
3
3
  import {clamp} from '@oscarpalmer/atoms/number';
4
+ import {setAria} from '@oscarpalmer/toretto/aria';
5
+ import {getAttribute, setAttribute} from '@oscarpalmer/toretto/attribute';
4
6
  import {getPosition, on} from '@oscarpalmer/toretto/event';
5
7
  import {findAncestor} from '@oscarpalmer/toretto/find';
6
- import {type StyleToggler, toggleStyles} from '@oscarpalmer/toretto/style';
8
+ import {setStyle, type StyleToggler, toggleStyles} from '@oscarpalmer/toretto/style';
7
9
  import supportsTouch from '@oscarpalmer/toretto/touch';
8
10
 
9
11
  declare global {
@@ -78,11 +80,7 @@ class OuiSplitter {
78
80
  RESIZE_OBSERVER.unobserve(this.element);
79
81
  }
80
82
 
81
- if (this.types.horizontal) {
82
- this.element.setAttribute(ATTRIBUTE_HORIZONTAL, '');
83
- } else {
84
- this.element.removeAttribute(ATTRIBUTE_HORIZONTAL);
85
- }
83
+ setAttribute(this.element, ATTRIBUTE_HORIZONTAL, this.types.horizontal ? '' : undefined);
86
84
 
87
85
  setAriaOrientation(this);
88
86
  }
@@ -120,7 +118,9 @@ export class OuiSplitterElement extends HTMLElement {
120
118
  * Maximum size for the first panel
121
119
  */
122
120
  set max(value: number) {
123
- this.setAttribute(ATTRIBUTE_MAX, String(value));
121
+ if (typeof value === 'number') {
122
+ setAttribute(this, ATTRIBUTE_MAX, value.toString());
123
+ }
124
124
  }
125
125
 
126
126
  /**
@@ -134,7 +134,9 @@ export class OuiSplitterElement extends HTMLElement {
134
134
  * Minimum size for the first panel
135
135
  */
136
136
  set min(value: number) {
137
- this.setAttribute(ATTRIBUTE_MIN, String(value));
137
+ if (typeof value === 'number') {
138
+ setAttribute(this, ATTRIBUTE_MIN, value.toString());
139
+ }
138
140
  }
139
141
 
140
142
  /**
@@ -148,21 +150,23 @@ export class OuiSplitterElement extends HTMLElement {
148
150
  * Current size for the first panel
149
151
  */
150
152
  set size(value: number) {
151
- this.setAttribute(ATTRIBUTE_SIZE, String(value));
153
+ if (typeof value === 'number') {
154
+ setAttribute(this, ATTRIBUTE_SIZE, value.toString());
155
+ }
152
156
  }
153
157
 
154
158
  /**
155
159
  * Orientation of the splitter
156
160
  */
157
161
  get type(): string {
158
- return this.getAttribute(ATTRIBUTE_TYPE) ?? 'vertical';
162
+ return getAttribute(this, ATTRIBUTE_TYPE) ?? 'vertical';
159
163
  }
160
164
 
161
165
  /**
162
166
  * Orientation of the splitter
163
167
  */
164
168
  set type(value: string) {
165
- this.setAttribute(ATTRIBUTE_TYPE, value);
169
+ setAttribute(this, ATTRIBUTE_TYPE, value);
166
170
  }
167
171
 
168
172
  // Constructor
@@ -170,7 +174,7 @@ export class OuiSplitterElement extends HTMLElement {
170
174
  constructor() {
171
175
  super();
172
176
 
173
- const panels = [...this.children].filter(child => child instanceof HTMLElement);
177
+ const panels = [...this.children].filter(child => child instanceof Element) as HTMLElement[];
174
178
 
175
179
  if (panels.length !== 2) {
176
180
  throw new Error(MESSAGE);
@@ -228,8 +232,8 @@ type OuiSplitterValuesNow = {
228
232
  function createHandle(): HTMLSpanElement {
229
233
  const handle = document.createElement('span');
230
234
 
231
- handle.setAttribute('aria-hidden', 'true');
232
- handle.setAttribute(ATTRIBUTE_HANDLE, '');
235
+ setAria(handle, 'hidden', 'true');
236
+ setAttribute(handle, ATTRIBUTE_HANDLE, '');
233
237
 
234
238
  return handle;
235
239
  }
@@ -240,9 +244,12 @@ function createSeparator(splitter: OuiSplitter): HTMLDivElement {
240
244
  separator.role = 'separator';
241
245
  separator.tabIndex = 0;
242
246
 
243
- separator.setAttribute('aria-controls', splitter.element.id);
244
- separator.setAttribute('aria-label', 'Resize panels');
245
- separator.setAttribute(ATTRIBUTE_SEPARATOR, '');
247
+ setAria(separator, {
248
+ controls: splitter.element.id,
249
+ label: 'Resize panels',
250
+ });
251
+
252
+ setAttribute(separator, ATTRIBUTE_SEPARATOR, '');
246
253
 
247
254
  return separator;
248
255
  }
@@ -262,7 +269,7 @@ function getSize(splitter: OuiSplitter, value: number): number {
262
269
  }
263
270
 
264
271
  function getTypes(splitter: OuiSplitter, width?: number, height?: number): OuiSplitterTypes {
265
- const type = splitter.element.getAttribute('type')?.trim() ?? '';
272
+ const type = getAttribute(splitter.element, 'type');
266
273
 
267
274
  if (type === 'horizontal') {
268
275
  return {
@@ -285,7 +292,7 @@ function getTypes(splitter: OuiSplitter, width?: number, height?: number): OuiSp
285
292
  }
286
293
 
287
294
  function getValue(element: HTMLElement, name: string, defaultValue: number): number {
288
- const value = element.getAttribute(name);
295
+ const value = getAttribute(element, name);
289
296
 
290
297
  if (isNullableOrWhitespace(value)) {
291
298
  return defaultValue;
@@ -408,11 +415,11 @@ function onObservation(entries: ResizeObserverEntry[]): void {
408
415
 
409
416
  splitter.types = getTypes(splitter, entry.contentRect.width, entry.contentRect.height);
410
417
 
411
- if (splitter.types.horizontal) {
412
- splitter.element.setAttribute(ATTRIBUTE_HORIZONTAL, '');
413
- } else {
414
- splitter.element.removeAttribute(ATTRIBUTE_HORIZONTAL);
415
- }
418
+ setAttribute(
419
+ splitter.element,
420
+ ATTRIBUTE_HORIZONTAL,
421
+ splitter.types.horizontal ? '' : undefined,
422
+ );
416
423
 
417
424
  setAriaOrientation(splitter);
418
425
  }
@@ -436,8 +443,8 @@ function onPointerdown(event: Event): void {
436
443
  event.preventDefault();
437
444
  }
438
445
 
439
- splitter.handle.setAttribute(ATTRIBUTE_ACTIVE, '');
440
- splitter.separator.setAttribute(ATTRIBUTE_ACTIVE, '');
446
+ setAttribute(splitter.handle, ATTRIBUTE_ACTIVE, '');
447
+ setAttribute(splitter.separator, ATTRIBUTE_ACTIVE, '');
441
448
 
442
449
  splitter.rectangle = splitter.element.getBoundingClientRect();
443
450
 
@@ -468,17 +475,15 @@ function onPointerup(): void {
468
475
  }
469
476
 
470
477
  function setAriaOrientation(splitter: OuiSplitter): void {
471
- splitter.separator.setAttribute(
472
- 'aria-orientation',
473
- splitter.types.horizontal ? 'horizontal' : 'vertical',
474
- );
478
+ setAria(splitter.separator, 'orientation', splitter.types.horizontal ? 'horizontal' : 'vertical');
475
479
  }
476
480
 
477
481
  function setAriaValue(splitter: OuiSplitter): void {
478
- splitter.separator.setAttribute('aria-valuemax', String(splitter.values.max));
479
- splitter.separator.setAttribute('aria-valuemin', String(splitter.values.min));
480
-
481
- splitter.separator.setAttribute('aria-valuenow', String(splitter.values.now.current));
482
+ setAria(splitter.separator, {
483
+ valuemax: splitter.values.max,
484
+ valuemin: splitter.values.min,
485
+ valuenow: splitter.values.now.current,
486
+ });
482
487
  }
483
488
 
484
489
  function setSize(splitter: OuiSplitter, size: number, previous?: boolean): void {
@@ -488,7 +493,7 @@ function setSize(splitter: OuiSplitter, size: number, previous?: boolean): void
488
493
  splitter.values.now.previous = size;
489
494
  }
490
495
 
491
- splitter.element.style.setProperty('--oui-splitter-size', `${size}`);
496
+ setStyle(splitter.element, '--oui-splitter-size', `${size}`);
492
497
 
493
498
  setAriaValue(splitter);
494
499
  }
@@ -557,5 +562,5 @@ on(document, 'keydown', onKeydown);
557
562
  on(document, 'mousedown', onPointerdown);
558
563
  on(document, 'pointermove', onPointermove);
559
564
  on(document, 'pointerup', onPointerup);
560
- on(document, 'touchcancel', onPointerup, {passive: false});
565
+ on(document, 'touchcancel', onPointerup);
561
566
  on(document, 'touchstart', onPointerdown, {passive: false});