@oscarpalmer/oui 0.16.0 → 0.18.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 (62) hide show
  1. package/dist/css/badge.css +3 -3
  2. package/dist/css/content.css +9 -11
  3. package/dist/css/form.css +36 -22
  4. package/dist/css/message.css +4 -0
  5. package/dist/css/popover.css +3 -0
  6. package/dist/css/reset.css +176 -56
  7. package/dist/css/splitter.css +3 -3
  8. package/dist/css/tooltip.css +8 -0
  9. package/dist/js/attributable.full.js +48 -0
  10. package/dist/js/attributable.js +30 -45
  11. package/dist/js/floatable.full.js +413 -0
  12. package/dist/js/floatable.js +259 -132
  13. package/dist/js/focus-trap/embedded.full.js +351 -0
  14. package/dist/js/focus-trap/embedded.js +131 -131
  15. package/dist/js/focus-trap/standalone.full.js +402 -0
  16. package/dist/js/focus-trap/standalone.js +4 -4
  17. package/dist/js/index.js +4 -5
  18. package/dist/js/oui.full.js +1596 -0
  19. package/dist/js/popover.full.js +822 -0
  20. package/dist/js/popover.js +83 -156
  21. package/dist/js/splitter.full.js +605 -0
  22. package/dist/js/splitter.js +279 -200
  23. package/dist/js/tooltip.full.js +679 -0
  24. package/dist/js/tooltip.js +159 -144
  25. package/package.json +31 -59
  26. package/src/css/content.scss +1 -2
  27. package/src/css/form.scss +12 -4
  28. package/src/css/message.scss +3 -0
  29. package/src/css/mixins.scss +7 -0
  30. package/src/css/popover.scss +4 -0
  31. package/src/css/reset.scss +180 -54
  32. package/src/css/splitter.scss +3 -3
  33. package/src/css/tooltip.scss +8 -0
  34. package/src/js/floatable.ts +458 -124
  35. package/src/js/focus-trap/embedded.ts +149 -64
  36. package/src/js/focus-trap/standalone.ts +4 -4
  37. package/src/js/index.ts +1 -0
  38. package/src/js/popover.ts +51 -114
  39. package/src/js/splitter.ts +346 -109
  40. package/src/js/tooltip.ts +157 -63
  41. package/types/floatable.d.ts +19 -7
  42. package/types/focus-trap/embedded.d.ts +18 -5
  43. package/types/index.d.ts +1 -0
  44. package/types/popover.d.ts +2 -0
  45. package/types/splitter.d.ts +13 -1
  46. package/types/tooltip.d.ts +2 -1
  47. package/dist/js/attributable.cjs +0 -51
  48. package/dist/js/floatable.cjs +0 -134
  49. package/dist/js/focus-trap/embedded.cjs +0 -150
  50. package/dist/js/focus-trap/standalone.cjs +0 -8
  51. package/dist/js/index.cjs +0 -6
  52. package/dist/js/popover.cjs +0 -160
  53. package/dist/js/splitter.cjs +0 -211
  54. package/dist/js/tooltip.cjs +0 -160
  55. package/types/attributable.d.cts +0 -5
  56. package/types/floatable.d.cts +0 -16
  57. package/types/focus-trap/embedded.d.cts +0 -15
  58. package/types/focus-trap/standalone.d.cts +0 -3
  59. package/types/index.d.cts +0 -14
  60. package/types/popover.d.cts +0 -14
  61. package/types/splitter.d.cts +0 -10
  62. package/types/tooltip.d.cts +0 -3
@@ -1,211 +1,290 @@
1
- import { clamp } from "@oscarpalmer/atoms/number";
2
- import { on, getPosition } from "@oscarpalmer/toretto/event";
1
+ import { isNullableOrWhitespace } from "@oscarpalmer/atoms/is";
2
+ import { getPosition, on } from "@oscarpalmer/toretto/event";
3
3
  import { findAncestor } from "@oscarpalmer/toretto/find";
4
- class OuiSplitterElement extends HTMLElement {
5
- #splitter;
6
- constructor() {
7
- super();
8
- const panels = [...this.children].filter(
9
- (child) => child instanceof HTMLElement
10
- );
11
- if (panels.length !== 2) {
12
- throw new Error("oui-splitter must have exactly two panels.");
13
- }
14
- this.#splitter = new Splitter(this, panels);
15
- }
16
- connectedCallback() {
17
- if (this.#splitter.types.auto) {
18
- observer.observe(this);
19
- }
20
- }
21
- disconnectedCallback() {
22
- if (this.#splitter.types.auto) {
23
- observer.unobserve(this);
24
- }
25
- }
26
- }
27
- class Splitter {
28
- constructor(element, panels) {
29
- this.element = element;
30
- this.panels = panels;
31
- this.handle = createHandle();
32
- this.rectangle = element.getBoundingClientRect();
33
- this.separator = createSeparator();
34
- this.separator.append(this.handle);
35
- panels[0].insertAdjacentElement("afterend", this.separator);
36
- mapped.set(element, this);
37
- mapped.set(this.handle, this);
38
- setSize(this, this.flex.value);
39
- this.types = getTypes(this);
40
- if (this.types.horizontal) {
41
- this.element.setAttribute("oui-splitter-horizontal", "");
42
- }
43
- }
44
- flex = {
45
- previous: 0.5,
46
- value: 0.5
47
- };
48
- handle;
49
- rectangle;
50
- separator;
51
- types;
52
- }
4
+ import { round } from "@oscarpalmer/atoms/math";
5
+ import { clamp } from "@oscarpalmer/atoms/number";
6
+ import { toggleStyles } from "@oscarpalmer/toretto/style";
7
+ import supportsTouch from "@oscarpalmer/toretto/touch";
8
+ var OuiSplitterElement = class extends HTMLElement {
9
+ static observedAttributes = [
10
+ "max",
11
+ "min",
12
+ "size",
13
+ "type"
14
+ ];
15
+ #splitter;
16
+ get max() {
17
+ return this.#splitter.values.max;
18
+ }
19
+ set max(value) {
20
+ this.setAttribute("max", String(value));
21
+ }
22
+ get min() {
23
+ return this.#splitter.values.min;
24
+ }
25
+ set min(value) {
26
+ this.setAttribute("min", String(value));
27
+ }
28
+ get size() {
29
+ return this.#splitter.values.now.current;
30
+ }
31
+ set size(value) {
32
+ this.setAttribute("size", String(value));
33
+ }
34
+ get type() {
35
+ return this.getAttribute("type") ?? "vertical";
36
+ }
37
+ set type(value) {
38
+ this.setAttribute("type", value);
39
+ }
40
+ constructor() {
41
+ super();
42
+ const panels = [...this.children].filter((child) => child instanceof HTMLElement);
43
+ if (panels.length !== 2) throw new Error(`${SELECTOR} must have exactly two panels.`);
44
+ this.#splitter = new Splitter(this, panels);
45
+ }
46
+ connectedCallback() {
47
+ this.#splitter.updateConnections(1);
48
+ }
49
+ disconnectedCallback() {
50
+ this.#splitter.updateConnections(0);
51
+ }
52
+ attributeChangedCallback(name) {
53
+ switch (name) {
54
+ case "max":
55
+ case "min":
56
+ case "size":
57
+ this.#splitter.updateValues();
58
+ break;
59
+ case "type":
60
+ this.#splitter.updateOrientation();
61
+ break;
62
+ default: break;
63
+ }
64
+ }
65
+ };
66
+ var index = 0;
67
+ var Splitter = class {
68
+ element;
69
+ handle;
70
+ panels;
71
+ rectangle;
72
+ separator;
73
+ types;
74
+ values;
75
+ constructor(element, panels) {
76
+ this.element = element;
77
+ this.panels = panels;
78
+ if (isNullableOrWhitespace(element.id)) element.id = `oui_splitter_${++index}`;
79
+ this.values = getValues(element);
80
+ this.handle = createHandle();
81
+ this.rectangle = element.getBoundingClientRect();
82
+ this.separator = createSeparator(this);
83
+ this.separator.append(this.handle);
84
+ panels[0].insertAdjacentElement("afterend", this.separator);
85
+ this.updateOrientation();
86
+ this.updateValues();
87
+ }
88
+ updateConnections(add) {
89
+ const method = METHODS_CONNECT[add];
90
+ MAPPED_ELEMENTS[method](this.element, this);
91
+ MAPPED_ELEMENTS[method](this.handle, this);
92
+ MAPPED_ELEMENTS[method](this.separator, this);
93
+ if (this.types.auto) RESIZE_OBSERVER[METHODS_OBSERVE[add]](this.element);
94
+ }
95
+ updateOrientation() {
96
+ this.types = getTypes(this);
97
+ if (this.types.auto) RESIZE_OBSERVER.observe(this.element);
98
+ else RESIZE_OBSERVER.unobserve(this.element);
99
+ if (this.types.horizontal) this.element.setAttribute(ATTRIBUTE_HORIZONTAL, "");
100
+ else this.element.removeAttribute(ATTRIBUTE_HORIZONTAL);
101
+ setAriaOrientation(this);
102
+ }
103
+ updateValues() {
104
+ this.values = getValues(this.element);
105
+ requestAnimationFrame(() => {
106
+ setSize(this, this.values.now.current);
107
+ setAriaValue(this);
108
+ });
109
+ }
110
+ };
53
111
  function createHandle() {
54
- const handle = document.createElement("span");
55
- handle.tabIndex = 0;
56
- handle.setAttribute("aria-label", "Resize panels");
57
- handle.setAttribute("oui-splitter-handle", "");
58
- return handle;
59
- }
60
- function createSeparator() {
61
- const separator = document.createElement("div");
62
- separator.setAttribute("oui-splitter-separator", "");
63
- return separator;
64
- }
65
- function getFlex(splitter2, value) {
66
- const { height, left, top, width } = splitter2.rectangle;
67
- const offset = value - (splitter2.types.horizontal ? top : left);
68
- const fraction = offset / (splitter2.types.horizontal ? height : width);
69
- return clamp(fraction, 0.1, 0.9);
70
- }
71
- function getTypes(splitter2, width, height) {
72
- const type = splitter2.element.getAttribute("type")?.trim() ?? "";
73
- if (type === "horizontal") {
74
- return {
75
- auto: false,
76
- horizontal: true
77
- };
78
- }
79
- if (type === "auto") {
80
- return {
81
- auto: true,
82
- horizontal: (height ?? splitter2.rectangle.height) > (width ?? splitter2.rectangle.width)
83
- };
84
- }
85
- return {
86
- auto: false,
87
- horizontal: false
88
- };
112
+ const handle = document.createElement("span");
113
+ handle.setAttribute("aria-hidden", "true");
114
+ handle.setAttribute(ATTRIBUTE_HANDLE, "");
115
+ return handle;
116
+ }
117
+ function createSeparator(splitter$1) {
118
+ const separator = document.createElement("div");
119
+ separator.role = "separator";
120
+ separator.tabIndex = 0;
121
+ separator.setAttribute("aria-controls", splitter$1.element.id);
122
+ separator.setAttribute("aria-label", "Resize panels");
123
+ separator.setAttribute(ATTRIBUTE_SEPARATOR, "");
124
+ return separator;
125
+ }
126
+ function getContained(min, value, max) {
127
+ return round(clamp(value, min, max), VALUE_DECIMALS);
128
+ }
129
+ function getSize(splitter$1, value) {
130
+ const { height, left, top, width } = splitter$1.rectangle;
131
+ const fraction = (value - (splitter$1.types.horizontal ? top : left)) / (splitter$1.types.horizontal ? height : width);
132
+ return getContained(splitter$1.values.min, fraction, splitter$1.values.max);
133
+ }
134
+ function getTypes(splitter$1, width, height) {
135
+ const type = splitter$1.element.getAttribute("type")?.trim() ?? "";
136
+ if (type === "horizontal") return {
137
+ auto: false,
138
+ horizontal: true
139
+ };
140
+ if (type === "auto") return {
141
+ auto: true,
142
+ horizontal: (height ?? splitter$1.rectangle.height) > (width ?? splitter$1.rectangle.width)
143
+ };
144
+ return {
145
+ auto: false,
146
+ horizontal: false
147
+ };
148
+ }
149
+ function getValue(element, name, defaultValue) {
150
+ const value = element.getAttribute(name);
151
+ if (isNullableOrWhitespace(value)) return defaultValue;
152
+ const parsed = Number.parseFloat(value);
153
+ if (Number.isNaN(parsed)) return defaultValue;
154
+ if (parsed < 0) return defaultValue;
155
+ return parsed > 1 ? parsed / NAVIGATION_MAXIMUM_TOTAL : parsed;
156
+ }
157
+ function getValues(element) {
158
+ const values = {
159
+ max: getValue(element, "max", NAVIGATION_MAXIMUM_PERCENTAGE),
160
+ min: getValue(element, "min", NAVIGATION_MINIMUM_PERCENTAGE),
161
+ now: {
162
+ current: .5,
163
+ previous: .5
164
+ }
165
+ };
166
+ const value = getContained(values.min, getValue(element, "size", values.now.current), values.max);
167
+ values.now.current = value;
168
+ values.now.previous = value;
169
+ return values;
89
170
  }
90
171
  function onEnd(reset) {
91
- if (splitter != null) {
92
- splitter.handle.removeAttribute("oui-splitter-active");
93
- splitter.separator.removeAttribute("oui-splitter-active");
94
- if (reset) {
95
- setSize(splitter, splitter.flex.previous);
96
- } else {
97
- splitter.flex.previous = splitter.flex.value;
98
- }
99
- }
100
- splitter = void 0;
172
+ if (splitter == null) return;
173
+ splitter.handle.removeAttribute(ATTRIBUTE_ACTIVE);
174
+ splitter.separator.removeAttribute(ATTRIBUTE_ACTIVE);
175
+ if (reset) setSize(splitter, splitter.values.now.previous);
176
+ else splitter.values.now.previous = splitter.values.now.current;
177
+ splitter = void 0;
178
+ STYLING_TOGGLER.remove();
101
179
  }
102
180
  function onKeydown(event) {
103
- switch (true) {
104
- case allKeys.has(event.key):
105
- onNavigate(event);
106
- break;
107
- case event.key === "Escape":
108
- onEnd(true);
109
- break;
110
- }
111
- }
112
- function onMousedown(event) {
113
- splitter = mapped.get(
114
- findAncestor(
115
- event.target,
116
- "[oui-splitter-handle]"
117
- )
118
- );
119
- if (splitter == null) {
120
- return;
121
- }
122
- splitter.handle.setAttribute("oui-splitter-active", "");
123
- splitter.separator.setAttribute("oui-splitter-active", "");
124
- splitter.rectangle = splitter.element.getBoundingClientRect();
125
- }
126
- function onMousemove(event) {
127
- if (splitter == null) {
128
- return;
129
- }
130
- const position = getPosition(event);
131
- if (position != null) {
132
- splitter.flex.value = getFlex(
133
- splitter,
134
- splitter.types.horizontal ? position.y : position.x
135
- );
136
- setSize(splitter, splitter.flex.value);
137
- }
138
- }
139
- function onMouseup() {
140
- if (splitter != null) {
141
- onEnd(false);
142
- }
181
+ switch (true) {
182
+ case KEYS_ALL.has(event.key):
183
+ onNavigate(event);
184
+ break;
185
+ case event.key === "Escape":
186
+ onEnd(true);
187
+ break;
188
+ default: break;
189
+ }
143
190
  }
144
191
  function onNavigate(event) {
145
- const splitter2 = mapped.get(
146
- findAncestor(
147
- event.target,
148
- "[oui-splitter-handle]"
149
- )
150
- );
151
- if (splitter2 == null) {
152
- return;
153
- }
154
- if (absoluteKeys.has(event.key)) {
155
- setSize(splitter2, event.key === "End" ? 0.9 : 0.1, true);
156
- return;
157
- }
158
- if (splitter2.types.horizontal && !horizontalKeys.has(event.key) || !splitter2.types.horizontal && !verticalKeys.has(event.key)) {
159
- return;
160
- }
161
- const modifier = negativeKeys.has(event.key) ? -1 : 1;
162
- setSize(splitter2, splitter2.flex.value + modifier * 0.05, true);
192
+ const splitter$1 = MAPPED_ELEMENTS.get(findAncestor(event.target, "[oui-splitter-separator]"));
193
+ if (splitter$1 == null) return;
194
+ const { types, values } = splitter$1;
195
+ if (KEYS_ABSOLUTE.has(event.key)) {
196
+ setSize(splitter$1, event.key === "End" ? values.max : values.min, true);
197
+ return;
198
+ }
199
+ if (types.horizontal && !KEYS_HORIZONTAL.has(event.key) || !(types.horizontal || KEYS_VERTICAL.has(event.key))) return;
200
+ const offset = KEYS_NEGATIVE.has(event.key) ? -1 : 1;
201
+ setSize(splitter$1, getContained(values.min, values.now.current + offset * NAVIGATION_MODIFIER, values.max), true);
163
202
  }
164
203
  function onObservation(entries) {
165
- if (frame != null) {
166
- cancelAnimationFrame(frame);
167
- }
168
- frame = requestAnimationFrame(() => {
169
- for (const entry of entries) {
170
- const splitter2 = mapped.get(entry.target);
171
- if (splitter2 == null) {
172
- return;
173
- }
174
- splitter2.types = getTypes(
175
- splitter2,
176
- entry.contentRect.width,
177
- entry.contentRect.height
178
- );
179
- if (splitter2.types.horizontal) {
180
- splitter2.element.setAttribute("oui-splitter-horizontal", "");
181
- } else {
182
- splitter2.element.removeAttribute("oui-splitter-horizontal");
183
- }
184
- }
185
- frame = void 0;
186
- });
187
- }
188
- function setSize(splitter2, size, previous) {
189
- splitter2.flex.value = size;
190
- if (previous) {
191
- splitter2.flex.previous = size;
192
- }
193
- splitter2.element.style.setProperty("--oui-splitter-size", `${size}`);
194
- }
195
- const absoluteKeys = /* @__PURE__ */ new Set(["End", "Home"]);
196
- const horizontalKeys = /* @__PURE__ */ new Set(["ArrowDown", "ArrowUp"]);
197
- const mapped = /* @__PURE__ */ new WeakMap();
198
- const observer = new ResizeObserver(onObservation);
199
- const negativeKeys = /* @__PURE__ */ new Set(["ArrowLeft", "ArrowUp"]);
200
- const verticalKeys = /* @__PURE__ */ new Set(["ArrowLeft", "ArrowRight"]);
201
- const allKeys = /* @__PURE__ */ new Set([...absoluteKeys, ...horizontalKeys, ...verticalKeys]);
202
- let frame;
203
- let splitter;
204
- customElements.define("oui-splitter", OuiSplitterElement);
204
+ if (frame != null) cancelAnimationFrame(frame);
205
+ frame = requestAnimationFrame(() => {
206
+ for (const entry of entries) {
207
+ const splitter$1 = MAPPED_ELEMENTS.get(entry.target);
208
+ if (splitter$1 == null) return;
209
+ splitter$1.types = getTypes(splitter$1, entry.contentRect.width, entry.contentRect.height);
210
+ if (splitter$1.types.horizontal) splitter$1.element.setAttribute(ATTRIBUTE_HORIZONTAL, "");
211
+ else splitter$1.element.removeAttribute(ATTRIBUTE_HORIZONTAL);
212
+ setAriaOrientation(splitter$1);
213
+ }
214
+ frame = void 0;
215
+ });
216
+ }
217
+ function onPointerdown(event) {
218
+ if (event.type === "mousedown" && supportsTouch.value || event.type === "touchstart" && !supportsTouch.value) return;
219
+ splitter = MAPPED_ELEMENTS.get(findAncestor(event.target, SELECTOR_HANDLE));
220
+ if (splitter == null) return;
221
+ if (supportsTouch.value) event.preventDefault();
222
+ splitter.handle.setAttribute(ATTRIBUTE_ACTIVE, "");
223
+ splitter.separator.setAttribute(ATTRIBUTE_ACTIVE, "");
224
+ splitter.rectangle = splitter.element.getBoundingClientRect();
225
+ STYLING_TOGGLER.set();
226
+ }
227
+ function onPointermove(event) {
228
+ if (splitter == null) return;
229
+ const position = getPosition(event);
230
+ if (position != null) {
231
+ splitter.values.now.current = getSize(splitter, splitter.types.horizontal ? position.y : position.x);
232
+ setSize(splitter, splitter.values.now.current);
233
+ }
234
+ }
235
+ function onPointerup() {
236
+ if (splitter != null) onEnd(false);
237
+ }
238
+ function setAriaOrientation(splitter$1) {
239
+ splitter$1.separator.setAttribute("aria-orientation", splitter$1.types.horizontal ? "horizontal" : "vertical");
240
+ }
241
+ function setAriaValue(splitter$1) {
242
+ splitter$1.separator.setAttribute("aria-valuemax", String(splitter$1.values.max));
243
+ splitter$1.separator.setAttribute("aria-valuemin", String(splitter$1.values.min));
244
+ splitter$1.separator.setAttribute("aria-valuenow", String(splitter$1.values.now.current));
245
+ }
246
+ function setSize(splitter$1, size, previous) {
247
+ splitter$1.values.now.current = size;
248
+ if (previous) splitter$1.values.now.previous = size;
249
+ splitter$1.element.style.setProperty("--oui-splitter-size", `${size}`);
250
+ setAriaValue(splitter$1);
251
+ }
252
+ var SELECTOR = "oui-splitter";
253
+ var ATTRIBUTE_ACTIVE = `${SELECTOR}-active`;
254
+ var ATTRIBUTE_HANDLE = `${SELECTOR}-handle`;
255
+ var ATTRIBUTE_HORIZONTAL = `${SELECTOR}-horizontal`;
256
+ var ATTRIBUTE_SEPARATOR = `${SELECTOR}-separator`;
257
+ var KEYS_ABSOLUTE = new Set(["End", "Home"]);
258
+ var KEYS_HORIZONTAL = new Set(["ArrowDown", "ArrowUp"]);
259
+ var KEYS_NEGATIVE = new Set(["ArrowLeft", "ArrowUp"]);
260
+ var KEYS_VERTICAL = new Set(["ArrowLeft", "ArrowRight"]);
261
+ var KEYS_ALL = new Set([
262
+ ...KEYS_ABSOLUTE,
263
+ ...KEYS_HORIZONTAL,
264
+ ...KEYS_VERTICAL
265
+ ]);
266
+ var MAPPED_ELEMENTS = /* @__PURE__ */ new WeakMap();
267
+ var METHODS_CONNECT = ["delete", "set"];
268
+ var METHODS_OBSERVE = ["unobserve", "observe"];
269
+ var NAVIGATION_MAXIMUM_PERCENTAGE = .9;
270
+ var NAVIGATION_MAXIMUM_TOTAL = 100;
271
+ var NAVIGATION_MINIMUM_PERCENTAGE = .1;
272
+ var NAVIGATION_MODIFIER = .05;
273
+ var RESIZE_OBSERVER = new ResizeObserver(onObservation);
274
+ var SELECTOR_HANDLE = `[${ATTRIBUTE_HANDLE}]`;
275
+ var STYLING_TOGGLER = toggleStyles(document.body, {
276
+ touchAction: "none",
277
+ userSelect: "none",
278
+ webkitUserSelect: "none"
279
+ });
280
+ var VALUE_DECIMALS = 6;
281
+ var frame;
282
+ var splitter;
283
+ customElements.define(SELECTOR, OuiSplitterElement);
205
284
  on(document, "keydown", onKeydown);
206
- on(document, "mousedown", onMousedown);
207
- on(document, "mousemove", onMousemove);
208
- on(document, "mouseup", onMouseup);
209
- export {
210
- OuiSplitterElement
211
- };
285
+ on(document, "mousedown", onPointerdown);
286
+ on(document, "pointermove", onPointermove);
287
+ on(document, "pointerup", onPointerup);
288
+ on(document, "touchcancel", onPointerup, { passive: false });
289
+ on(document, "touchstart", onPointerdown, { passive: false });
290
+ export { OuiSplitterElement };