@oscarpalmer/oui 0.16.0 → 0.17.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,3 +1,5 @@
1
+ import {isNullableOrWhitespace} from '@oscarpalmer/atoms/is';
2
+ import {round} from '@oscarpalmer/atoms/math';
1
3
  import {clamp} from '@oscarpalmer/atoms/number';
2
4
  import {getPosition, on} from '@oscarpalmer/toretto/event';
3
5
  import {findAncestor} from '@oscarpalmer/toretto/find';
@@ -8,11 +10,6 @@ declare global {
8
10
  }
9
11
  }
10
12
 
11
- type Flex = {
12
- previous: number;
13
- value: number;
14
- };
15
-
16
13
  export class OuiSplitterElement extends HTMLElement {
17
14
  #splitter: Splitter;
18
15
 
@@ -43,12 +40,9 @@ export class OuiSplitterElement extends HTMLElement {
43
40
  }
44
41
  }
45
42
 
46
- class Splitter {
47
- flex: Flex = {
48
- previous: 0.5,
49
- value: 0.5,
50
- };
43
+ let index = 0;
51
44
 
45
+ class Splitter {
52
46
  handle: HTMLSpanElement;
53
47
 
54
48
  rectangle: DOMRect;
@@ -57,13 +51,21 @@ class Splitter {
57
51
 
58
52
  types: Types;
59
53
 
54
+ values: Values;
55
+
60
56
  constructor(
61
57
  public element: OuiSplitterElement,
62
58
  public panels: HTMLElement[],
63
59
  ) {
60
+ if (isNullableOrWhitespace(element.id)) {
61
+ element.id = `oui_splitter_${++index}`;
62
+ }
63
+
64
+ this.values = getValues(element);
65
+
64
66
  this.handle = createHandle();
65
67
  this.rectangle = element.getBoundingClientRect();
66
- this.separator = createSeparator();
68
+ this.separator = createSeparator(this);
67
69
 
68
70
  this.separator.append(this.handle);
69
71
 
@@ -72,13 +74,16 @@ class Splitter {
72
74
  mapped.set(element, this);
73
75
  mapped.set(this.handle, this);
74
76
 
75
- setSize(this, this.flex.value);
77
+ setSize(this, this.values.now.current);
76
78
 
77
79
  this.types = getTypes(this);
78
80
 
79
81
  if (this.types.horizontal) {
80
82
  this.element.setAttribute('oui-splitter-horizontal', '');
81
83
  }
84
+
85
+ setAriaOrientation(this);
86
+ setAriaValue(this);
82
87
  }
83
88
  }
84
89
 
@@ -87,35 +92,53 @@ type Types = {
87
92
  horizontal: boolean;
88
93
  };
89
94
 
95
+ type Values = {
96
+ max: number;
97
+ min: number;
98
+ now: ValuesNow;
99
+ };
100
+
101
+ type ValuesNow = {
102
+ current: number;
103
+ previous: number;
104
+ };
105
+
90
106
  //
91
107
 
92
108
  function createHandle(): HTMLSpanElement {
93
109
  const handle = document.createElement('span');
94
110
 
95
- handle.tabIndex = 0;
96
-
97
- handle.setAttribute('aria-label', 'Resize panels');
111
+ handle.setAttribute('aria-hidden', 'true');
98
112
  handle.setAttribute('oui-splitter-handle', '');
99
113
 
100
114
  return handle;
101
115
  }
102
116
 
103
- function createSeparator(): HTMLDivElement {
117
+ function createSeparator(splitter: Splitter): HTMLDivElement {
104
118
  const separator = document.createElement('div');
105
119
 
120
+ separator.role = 'separator';
121
+ separator.tabIndex = 0;
122
+
123
+ separator.setAttribute('aria-controls', splitter.element.id);
124
+ separator.setAttribute('aria-label', 'Resize panels');
106
125
  separator.setAttribute('oui-splitter-separator', '');
107
126
 
108
127
  return separator;
109
128
  }
110
129
 
111
- function getFlex(splitter: Splitter, value: number): number {
130
+ function getContained(min: number, value: number, max: number): number {
131
+ return round(clamp(value, min, max), 6);
132
+ }
133
+
134
+ function getSize(splitter: Splitter, value: number): number {
112
135
  const {height, left, top, width} = splitter.rectangle;
113
136
 
114
137
  const offset = value - (splitter.types.horizontal ? top : left);
115
138
 
116
139
  const fraction = offset / (splitter.types.horizontal ? height : width);
117
140
 
118
- return clamp(fraction, 0.1, 0.9);
141
+ return getContained(splitter.values.min, fraction, splitter.values.max);
119
142
  }
120
143
 
121
144
  function getTypes(splitter: Splitter, width?: number, height?: number): Types {
@@ -143,15 +166,57 @@ function getTypes(splitter: Splitter, width?: number, height?: number): Types {
143
166
  };
144
167
  }
145
168
 
169
+ function getValue(
170
+ element: HTMLElement,
171
+ name: string,
172
+ defaultValue: number,
173
+ ): number {
174
+ const value = element.getAttribute(name);
175
+
176
+ if (isNullableOrWhitespace(value)) {
177
+ return defaultValue;
178
+ }
179
+
180
+ const parsed = Number.parseFloat(value);
181
+
182
+ if (Number.isNaN(parsed)) {
183
+ return defaultValue;
184
+ }
185
+
186
+ return parsed < 0 ? defaultValue : parsed > 1 ? parsed / 100 : parsed;
187
+ }
188
+
189
+ function getValues(element: OuiSplitterElement): Values {
190
+ const values: Values = {
191
+ max: getValue(element, 'max', 0.9),
192
+ min: getValue(element, 'min', 0.1),
193
+ now: {
194
+ current: 0.5,
195
+ previous: 0.5,
196
+ },
197
+ };
198
+
199
+ const value = getContained(
200
+ values.min,
201
+ getValue(element, 'size', values.now.current),
202
+ values.max,
203
+ );
204
+
205
+ values.now.current = value;
206
+ values.now.previous = value;
207
+
208
+ return values;
209
+ }
210
+
146
211
  function onEnd(reset: boolean): void {
147
212
  if (splitter != null) {
148
213
  splitter.handle.removeAttribute('oui-splitter-active');
149
214
  splitter.separator.removeAttribute('oui-splitter-active');
150
215
 
151
216
  if (reset) {
152
- setSize(splitter, splitter.flex.previous);
217
+ setSize(splitter, splitter.values.now.previous);
153
218
  } else {
154
- splitter.flex.previous = splitter.flex.value;
219
+ splitter.values.now.previous = splitter.values.now.current;
155
220
  }
156
221
  }
157
222
 
@@ -199,12 +264,12 @@ function onMousemove(event: MouseEvent): void {
199
264
  const position = getPosition(event);
200
265
 
201
266
  if (position != null) {
202
- splitter.flex.value = getFlex(
267
+ splitter.values.now.current = getSize(
203
268
  splitter,
204
269
  splitter.types.horizontal ? position.y : position.x,
205
270
  );
206
271
 
207
- setSize(splitter, splitter.flex.value);
272
+ setSize(splitter, splitter.values.now.current);
208
273
  }
209
274
  }
210
275
 
@@ -227,7 +292,11 @@ function onNavigate(event: KeyboardEvent): void {
227
292
  }
228
293
 
229
294
  if (absoluteKeys.has(event.key)) {
230
- setSize(splitter, event.key === 'End' ? 0.9 : 0.1, true);
295
+ setSize(
296
+ splitter,
297
+ event.key === 'End' ? splitter.values.max : splitter.values.min,
298
+ true,
299
+ );
231
300
 
232
301
  return;
233
302
  }
@@ -241,7 +310,7 @@ function onNavigate(event: KeyboardEvent): void {
241
310
 
242
311
  const modifier = negativeKeys.has(event.key) ? -1 : 1;
243
312
 
244
- setSize(splitter, splitter.flex.value + modifier * 0.05, true);
313
+ setSize(splitter, splitter.values.now.current + modifier * 0.05, true);
245
314
  }
246
315
 
247
316
  function onObservation(entries: ResizeObserverEntry[]): void {
@@ -268,20 +337,41 @@ function onObservation(entries: ResizeObserverEntry[]): void {
268
337
  } else {
269
338
  splitter.element.removeAttribute('oui-splitter-horizontal');
270
339
  }
340
+
341
+ setAriaOrientation(splitter);
271
342
  }
272
343
 
273
344
  frame = undefined;
274
345
  });
275
346
  }
276
347
 
348
+ function setAriaOrientation(splitter: Splitter): void {
349
+ splitter.separator.setAttribute(
350
+ 'aria-orientation',
351
+ splitter.types.horizontal ? 'horizontal' : 'vertical',
352
+ );
353
+ }
354
+
355
+ function setAriaValue(splitter: Splitter): void {
356
+ splitter.separator.setAttribute('aria-valuemax', String(splitter.values.max));
357
+ splitter.separator.setAttribute('aria-valuemin', String(splitter.values.min));
358
+
359
+ splitter.separator.setAttribute(
360
+ 'aria-valuenow',
361
+ String(splitter.values.now.current),
362
+ );
363
+ }
364
+
277
365
  function setSize(splitter: Splitter, size: number, previous?: boolean): void {
278
- splitter.flex.value = size;
366
+ splitter.values.now.current = size;
279
367
 
280
368
  if (previous) {
281
- splitter.flex.previous = size;
369
+ splitter.values.now.previous = size;
282
370
  }
283
371
 
284
372
  splitter.element.style.setProperty('--oui-splitter-size', `${size}`);
373
+
374
+ setAriaValue(splitter);
285
375
  }
286
376
 
287
377
  //
package/src/js/tooltip.ts CHANGED
@@ -20,7 +20,14 @@ class Tooltip {
20
20
 
21
21
  constructor(anchor: HTMLElement, content: Content) {
22
22
  this.attributes = content.attributes;
23
- this.floatable = new Floatable(anchor, content.element);
23
+
24
+ this.floatable = new Floatable(
25
+ anchor,
26
+ content.element,
27
+ `${selector}-position`,
28
+ 'vertical',
29
+ true,
30
+ );
24
31
 
25
32
  initialize(this);
26
33
  }
@@ -3,13 +3,17 @@
3
3
  export declare class Floatable {
4
4
  anchor: HTMLElement;
5
5
  content: HTMLElement;
6
+ readonly attribute: string;
7
+ readonly position: Position;
8
+ readonly preferAbove: boolean;
6
9
  activate: number | undefined;
7
10
  active: boolean;
8
11
  deactivate: number | undefined;
9
12
  frame: DOMHighResTimeStamp | undefined;
10
- constructor(anchor: HTMLElement, content: HTMLElement);
13
+ constructor(anchor: HTMLElement, content: HTMLElement, attribute: string, position: Position, preferAbove: boolean);
11
14
  destroy(): void;
12
15
  }
16
+ export type Position = "above" | "above-end" | "above-start" | "below" | "below-end" | "below-start" | "end" | "end-bottom" | "end-top" | "horizontal" | "horizontal-bottom" | "horizontal-top" | "start" | "start-bottom" | "start-top" | "vertical" | "vertical-end" | "vertical-start";
13
17
  export declare function activate(floatable: Floatable, active: Set<Floatable>, order?: Floatable[], time?: number): void;
14
18
  export declare function deactivate(floatable: Floatable, active: Set<Floatable>, order?: Floatable[], time?: number): void;
15
19
 
@@ -1,12 +1,17 @@
1
1
  export declare class Floatable {
2
2
  anchor: HTMLElement;
3
3
  content: HTMLElement;
4
+ readonly attribute: string;
5
+ readonly position: Position;
6
+ readonly preferAbove: boolean;
4
7
  activate: number | undefined;
5
8
  active: boolean;
6
9
  deactivate: number | undefined;
7
10
  frame: DOMHighResTimeStamp | undefined;
8
- constructor(anchor: HTMLElement, content: HTMLElement);
11
+ constructor(anchor: HTMLElement, content: HTMLElement, attribute: string, position: Position, preferAbove: boolean);
9
12
  destroy(): void;
10
13
  }
14
+ type Position = 'above' | 'above-end' | 'above-start' | 'below' | 'below-end' | 'below-start' | 'end' | 'end-bottom' | 'end-top' | 'horizontal' | 'horizontal-bottom' | 'horizontal-top' | 'start' | 'start-bottom' | 'start-top' | 'vertical' | 'vertical-end' | 'vertical-start';
11
15
  export declare function activate(floatable: Floatable, active: Set<Floatable>, order?: Floatable[], time?: number): void;
12
16
  export declare function deactivate(floatable: Floatable, active: Set<Floatable>, order?: Floatable[], time?: number): void;
17
+ export {};