@oscarpalmer/oui 0.11.0 → 0.13.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.
@@ -0,0 +1,51 @@
1
+ function attributable(attribute, add, remove) {
2
+ const observer = new MutationObserver((entries) => {
3
+ const { length } = entries;
4
+ for (let index = 0; index < length; index += 1) {
5
+ const entry = entries[index];
6
+ if (entry.type === "attributes" && entry.target instanceof HTMLElement) {
7
+ if (entry.target.hasAttribute(attribute)) {
8
+ handleAdded(attribute, [entry.target], add);
9
+ } else {
10
+ handleRemoved([entry.target], remove);
11
+ }
12
+ } else if (entry.type === "childList") {
13
+ handleAdded(attribute, entry.addedNodes, add);
14
+ handleRemoved(entry.removedNodes, remove);
15
+ }
16
+ }
17
+ });
18
+ observer.observe(document, {
19
+ attributeFilter: [attribute],
20
+ attributes: true,
21
+ childList: true,
22
+ subtree: true
23
+ });
24
+ requestAnimationFrame(() => {
25
+ handleAdded(attribute, document.querySelectorAll(`[${attribute}]`), add);
26
+ });
27
+ }
28
+ function handleAdded(attribute, nodes, add) {
29
+ const elements = [...nodes].filter(
30
+ (node) => node instanceof HTMLElement
31
+ );
32
+ const { length } = elements;
33
+ for (let index = 0; index < length; index += 1) {
34
+ const element = elements[index];
35
+ if (element.hasAttribute(attribute)) {
36
+ add(element);
37
+ }
38
+ }
39
+ }
40
+ function handleRemoved(nodes, remove) {
41
+ const elements = [...nodes].filter(
42
+ (node) => node instanceof HTMLElement
43
+ );
44
+ const { length } = elements;
45
+ for (let index = 0; index < length; index += 1) {
46
+ remove(elements[index]);
47
+ }
48
+ }
49
+ export {
50
+ attributable
51
+ };
@@ -1,18 +1,16 @@
1
1
  "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
- var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
2
  const number = require("@oscarpalmer/atoms/number");
6
3
  const event = require("@oscarpalmer/toretto/event");
7
4
  const find = require("@oscarpalmer/toretto/find");
8
5
  const focusable = require("@oscarpalmer/toretto/focusable");
6
+ const attributable = require("./attributable.cjs");
9
7
  class FocusTrap {
10
8
  constructor(element) {
11
- __publicField(this, "active", true);
12
9
  this.element = element;
13
10
  element.setAttribute("aria-modal", "true");
14
11
  element.setAttribute("tabindex", "-1");
15
12
  }
13
+ active = true;
16
14
  activate() {
17
15
  this.element.setAttribute("aria-modal", "true");
18
16
  this.active = true;
@@ -27,20 +25,12 @@ class FocusTrap {
27
25
  inactive.add(this);
28
26
  }
29
27
  }
30
- function addFocusTraps(nodes) {
31
- const elements = [...nodes].filter(
32
- (node) => node instanceof HTMLElement
33
- );
34
- const { length } = elements;
35
- for (let index = 0; index < length; index += 1) {
36
- const element = elements[index];
37
- if (element.hasAttribute(selector)) {
38
- focusTraps.set(element, new FocusTrap(element));
39
- }
28
+ function addFocusTrap(element) {
29
+ if (!focusTraps.has(element)) {
30
+ focusTraps.set(element, new FocusTrap(element));
40
31
  }
41
32
  }
42
33
  function onEscape(event2) {
43
- var _a;
44
34
  if (event2.key !== "Escape") {
45
35
  return;
46
36
  }
@@ -48,7 +38,7 @@ function onEscape(event2) {
48
38
  document.activeElement,
49
39
  `[${selector}]`
50
40
  );
51
- (_a = focusTraps.get(element)) == null ? void 0 : _a.inactivate();
41
+ focusTraps.get(element)?.inactivate();
52
42
  }
53
43
  function onFocusOut() {
54
44
  if (inactive.size === 0) {
@@ -67,7 +57,6 @@ function onFocusOut() {
67
57
  });
68
58
  }
69
59
  function onTab(event2) {
70
- var _a;
71
60
  if (event2.key !== "Tab") {
72
61
  return;
73
62
  }
@@ -75,7 +64,7 @@ function onTab(event2) {
75
64
  event2.target,
76
65
  `[${selector}]`
77
66
  );
78
- if (!(((_a = focusTraps.get(element)) == null ? void 0 : _a.active) ?? false)) {
67
+ if (!(focusTraps.get(element)?.active ?? false)) {
79
68
  return;
80
69
  }
81
70
  event2.preventDefault();
@@ -95,53 +84,21 @@ function onTab(event2) {
95
84
  elements[next].focus();
96
85
  }
97
86
  }
98
- function removeFocusTraps(nodes) {
99
- var _a;
100
- const elements = [...nodes].filter(
101
- (node) => node instanceof HTMLElement
102
- );
103
- const { length } = elements;
104
- for (let index = 0; index < length; index += 1) {
105
- const element = elements[index];
106
- (_a = focusTraps.get(element)) == null ? void 0 : _a.destroy();
107
- focusTraps.delete(element);
108
- }
87
+ function removeFocusTraps(element) {
88
+ focusTraps.get(element)?.destroy();
89
+ focusTraps.delete(element);
109
90
  }
110
91
  const focusTraps = /* @__PURE__ */ new WeakMap();
111
92
  const inactive = /* @__PURE__ */ new Set();
112
- const observer = new MutationObserver((entries) => {
113
- const { length } = entries;
114
- for (let index = 0; index < length; index += 1) {
115
- const entry = entries[index];
116
- if (entry.type === "attributes" && entry.target instanceof HTMLElement) {
117
- if (entry.target.hasAttribute(selector)) {
118
- addFocusTraps([entry.target]);
119
- } else {
120
- removeFocusTraps([entry.target]);
121
- }
122
- } else if (entry.type === "childList") {
123
- addFocusTraps(entry.addedNodes);
124
- removeFocusTraps(entry.removedNodes);
125
- }
126
- }
127
- });
128
93
  const selector = "oui-focus-trap";
129
- observer.observe(document, {
130
- attributeFilter: [selector],
131
- attributes: true,
132
- childList: true,
133
- subtree: true
94
+ attributable.attributable(selector, addFocusTrap, removeFocusTraps);
95
+ event.on(document, "focusout", onFocusOut, {
96
+ capture: true
134
97
  });
135
- requestAnimationFrame(() => {
136
- addFocusTraps(document.querySelectorAll(`[${selector}]`));
137
- event.on(document, "focusout", onFocusOut, {
138
- capture: true
139
- });
140
- event.on(document, "keydown", onTab, {
141
- capture: true,
142
- passive: false
143
- });
144
- event.on(document, "keydown", onEscape, {
145
- passive: false
146
- });
98
+ event.on(document, "keydown", onTab, {
99
+ capture: true,
100
+ passive: false
101
+ });
102
+ event.on(document, "keydown", onEscape, {
103
+ passive: false
147
104
  });
@@ -1,17 +1,15 @@
1
- var __defProp = Object.defineProperty;
2
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
- var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
1
  import { clamp } from "@oscarpalmer/atoms/number";
5
2
  import { on } from "@oscarpalmer/toretto/event";
6
3
  import { findAncestor } from "@oscarpalmer/toretto/find";
7
4
  import { getFocusable } from "@oscarpalmer/toretto/focusable";
5
+ import { attributable } from "./attributable.js";
8
6
  class FocusTrap {
9
7
  constructor(element) {
10
- __publicField(this, "active", true);
11
8
  this.element = element;
12
9
  element.setAttribute("aria-modal", "true");
13
10
  element.setAttribute("tabindex", "-1");
14
11
  }
12
+ active = true;
15
13
  activate() {
16
14
  this.element.setAttribute("aria-modal", "true");
17
15
  this.active = true;
@@ -26,20 +24,12 @@ class FocusTrap {
26
24
  inactive.add(this);
27
25
  }
28
26
  }
29
- function addFocusTraps(nodes) {
30
- const elements = [...nodes].filter(
31
- (node) => node instanceof HTMLElement
32
- );
33
- const { length } = elements;
34
- for (let index = 0; index < length; index += 1) {
35
- const element = elements[index];
36
- if (element.hasAttribute(selector)) {
37
- focusTraps.set(element, new FocusTrap(element));
38
- }
27
+ function addFocusTrap(element) {
28
+ if (!focusTraps.has(element)) {
29
+ focusTraps.set(element, new FocusTrap(element));
39
30
  }
40
31
  }
41
32
  function onEscape(event) {
42
- var _a;
43
33
  if (event.key !== "Escape") {
44
34
  return;
45
35
  }
@@ -47,7 +37,7 @@ function onEscape(event) {
47
37
  document.activeElement,
48
38
  `[${selector}]`
49
39
  );
50
- (_a = focusTraps.get(element)) == null ? void 0 : _a.inactivate();
40
+ focusTraps.get(element)?.inactivate();
51
41
  }
52
42
  function onFocusOut() {
53
43
  if (inactive.size === 0) {
@@ -66,7 +56,6 @@ function onFocusOut() {
66
56
  });
67
57
  }
68
58
  function onTab(event) {
69
- var _a;
70
59
  if (event.key !== "Tab") {
71
60
  return;
72
61
  }
@@ -74,7 +63,7 @@ function onTab(event) {
74
63
  event.target,
75
64
  `[${selector}]`
76
65
  );
77
- if (!(((_a = focusTraps.get(element)) == null ? void 0 : _a.active) ?? false)) {
66
+ if (!(focusTraps.get(element)?.active ?? false)) {
78
67
  return;
79
68
  }
80
69
  event.preventDefault();
@@ -94,53 +83,21 @@ function onTab(event) {
94
83
  elements[next].focus();
95
84
  }
96
85
  }
97
- function removeFocusTraps(nodes) {
98
- var _a;
99
- const elements = [...nodes].filter(
100
- (node) => node instanceof HTMLElement
101
- );
102
- const { length } = elements;
103
- for (let index = 0; index < length; index += 1) {
104
- const element = elements[index];
105
- (_a = focusTraps.get(element)) == null ? void 0 : _a.destroy();
106
- focusTraps.delete(element);
107
- }
86
+ function removeFocusTraps(element) {
87
+ focusTraps.get(element)?.destroy();
88
+ focusTraps.delete(element);
108
89
  }
109
90
  const focusTraps = /* @__PURE__ */ new WeakMap();
110
91
  const inactive = /* @__PURE__ */ new Set();
111
- const observer = new MutationObserver((entries) => {
112
- const { length } = entries;
113
- for (let index = 0; index < length; index += 1) {
114
- const entry = entries[index];
115
- if (entry.type === "attributes" && entry.target instanceof HTMLElement) {
116
- if (entry.target.hasAttribute(selector)) {
117
- addFocusTraps([entry.target]);
118
- } else {
119
- removeFocusTraps([entry.target]);
120
- }
121
- } else if (entry.type === "childList") {
122
- addFocusTraps(entry.addedNodes);
123
- removeFocusTraps(entry.removedNodes);
124
- }
125
- }
126
- });
127
92
  const selector = "oui-focus-trap";
128
- observer.observe(document, {
129
- attributeFilter: [selector],
130
- attributes: true,
131
- childList: true,
132
- subtree: true
93
+ attributable(selector, addFocusTrap, removeFocusTraps);
94
+ on(document, "focusout", onFocusOut, {
95
+ capture: true
133
96
  });
134
- requestAnimationFrame(() => {
135
- addFocusTraps(document.querySelectorAll(`[${selector}]`));
136
- on(document, "focusout", onFocusOut, {
137
- capture: true
138
- });
139
- on(document, "keydown", onTab, {
140
- capture: true,
141
- passive: false
142
- });
143
- on(document, "keydown", onEscape, {
144
- passive: false
145
- });
97
+ on(document, "keydown", onTab, {
98
+ capture: true,
99
+ passive: false
100
+ });
101
+ on(document, "keydown", onEscape, {
102
+ passive: false
146
103
  });
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+ const is = require("@oscarpalmer/atoms/is");
3
+ const event = require("@oscarpalmer/toretto/event");
4
+ const attributable = require("./attributable.cjs");
5
+ class Tooltip {
6
+ constructor(parent) {
7
+ this.parent = parent;
8
+ this.element = getContent(parent);
9
+ if (this.element != null) {
10
+ parent.insertAdjacentElement("afterend", this.element);
11
+ initialize(this);
12
+ }
13
+ }
14
+ abortController = new AbortController();
15
+ activate;
16
+ active = false;
17
+ deactivate;
18
+ element;
19
+ frame;
20
+ destroy() {
21
+ deactivate(this);
22
+ this.abortController.abort();
23
+ this.abortController = void 0;
24
+ this.activate = void 0;
25
+ this.deactivate = void 0;
26
+ this.element = void 0;
27
+ this.frame = void 0;
28
+ this.parent = void 0;
29
+ }
30
+ }
31
+ function activate(tooltip) {
32
+ stop(tooltip);
33
+ if (tooltip.active) {
34
+ return;
35
+ }
36
+ tooltip.activate = +setTimeout(() => {
37
+ tooltip.active = true;
38
+ active.add(tooltip);
39
+ document.body.append(tooltip.element);
40
+ tooltip.element.hidden = false;
41
+ update(tooltip);
42
+ }, 250);
43
+ }
44
+ function addTooltip(element) {
45
+ if (!tooltips.has(element)) {
46
+ tooltips.set(element, new Tooltip(element));
47
+ }
48
+ }
49
+ function deactivate(tooltip, time) {
50
+ stop(tooltip);
51
+ if (!tooltip.active) {
52
+ return;
53
+ }
54
+ tooltip.deactivate = +setTimeout(() => {
55
+ tooltip.active = false;
56
+ active.delete(tooltip);
57
+ tooltip.element.hidden = true;
58
+ tooltip.parent.insertAdjacentElement("afterend", tooltip.element);
59
+ }, time ?? 250);
60
+ }
61
+ function getContent(parent) {
62
+ const wrapper = getWrapper(parent);
63
+ const ids = (parent.getAttribute("aria-describedby") ?? "").split(/\s+/);
64
+ parent.setAttribute("aria-describedby", wrapper.id);
65
+ const elements = ids.map(
66
+ (id) => is.isNullableOrWhitespace(id) ? void 0 : document.querySelector(`#${id}`)
67
+ ).filter((element) => element instanceof HTMLElement);
68
+ if (elements.length > 0) {
69
+ for (const element of elements) {
70
+ element.hidden = false;
71
+ wrapper.append(element);
72
+ }
73
+ return wrapper;
74
+ }
75
+ let content = parent.getAttribute("aria-description");
76
+ if (is.isNullableOrWhitespace(content)) {
77
+ content = parent.getAttribute("aria-label");
78
+ if (is.isNullableOrWhitespace(content)) {
79
+ return;
80
+ }
81
+ }
82
+ const paragraph = document.createElement("p");
83
+ paragraph.textContent = content;
84
+ wrapper.append(paragraph);
85
+ return wrapper;
86
+ }
87
+ function getTop(parent, content) {
88
+ let top = parent.top - content.height - margin;
89
+ if (top - margin >= 0) {
90
+ return top;
91
+ }
92
+ top = parent.bottom + margin;
93
+ if (top + content.height + margin <= window.innerHeight) {
94
+ return top;
95
+ }
96
+ return parent.top + (parent.height - content.height) / 2;
97
+ }
98
+ function getWrapper(parent) {
99
+ const wrapper = document.createElement("div");
100
+ wrapper.setAttribute(`${selector}-content`, "");
101
+ wrapper.className = parent.getAttribute(`${selector}-class`) ?? "";
102
+ wrapper.hidden = true;
103
+ wrapper.id = `${selector}-${++index}`;
104
+ wrapper.role = "tooltip";
105
+ wrapper.tabIndex = -1;
106
+ return wrapper;
107
+ }
108
+ function getX(parent, content) {
109
+ if (content.width + 2 * margin >= window.innerWidth) {
110
+ return {
111
+ left: margin,
112
+ right: margin
113
+ };
114
+ }
115
+ const left = parent.left + (parent.width - content.width) / 2;
116
+ const right = window.innerWidth - margin;
117
+ if (left < margin) {
118
+ return {
119
+ left: margin
120
+ };
121
+ }
122
+ if (left + content.width > right) {
123
+ return {
124
+ right: margin
125
+ };
126
+ }
127
+ return {
128
+ left
129
+ };
130
+ }
131
+ function initialize(tooltip) {
132
+ const elements = [tooltip.parent, tooltip.element];
133
+ for (const element of elements) {
134
+ for (const event$1 of activateEvents) {
135
+ event.on(
136
+ element,
137
+ event$1,
138
+ () => {
139
+ activate(tooltip);
140
+ },
141
+ {
142
+ signal: tooltip.abortController.signal
143
+ }
144
+ );
145
+ }
146
+ for (const event$1 of deactivateEvents) {
147
+ event.on(
148
+ element,
149
+ event$1,
150
+ () => {
151
+ deactivate(tooltip);
152
+ },
153
+ {
154
+ signal: tooltip.abortController.signal
155
+ }
156
+ );
157
+ }
158
+ }
159
+ }
160
+ function removeTooltip(element) {
161
+ tooltips.get(element)?.destroy();
162
+ tooltips.delete(element);
163
+ }
164
+ function stop(tooltip) {
165
+ if (tooltip.frame != null) {
166
+ cancelAnimationFrame(tooltip.frame);
167
+ tooltip.frame = void 0;
168
+ }
169
+ if (tooltip.activate != null) {
170
+ clearTimeout(tooltip.activate);
171
+ tooltip.activate = void 0;
172
+ }
173
+ if (tooltip.deactivate != null) {
174
+ clearTimeout(tooltip.deactivate);
175
+ tooltip.deactivate = void 0;
176
+ }
177
+ }
178
+ function update(tooltip) {
179
+ function run() {
180
+ const parentRect = tooltip.parent.getBoundingClientRect();
181
+ const contentRect = tooltip.element.getBoundingClientRect();
182
+ const top = getTop(parentRect, contentRect);
183
+ const { left, right } = getX(parentRect, contentRect);
184
+ tooltip.element.style.inset = `${top}px ${right == null ? "auto" : `${right}px`} auto ${left == null ? "auto" : `${left}px`}`;
185
+ tooltip.frame = requestAnimationFrame(run);
186
+ }
187
+ tooltip.frame = requestAnimationFrame(run);
188
+ }
189
+ const active = /* @__PURE__ */ new Set();
190
+ const activateEvents = ["focus", "mouseenter", "touchstart"];
191
+ const deactivateEvents = ["blur", "mouseleave"];
192
+ const margin = Number.parseInt(getComputedStyle(document.documentElement).fontSize, 10) / 4;
193
+ const selector = "oui-tooltip";
194
+ const tooltips = /* @__PURE__ */ new WeakMap();
195
+ let index = 0;
196
+ attributable.attributable(selector, addTooltip, removeTooltip);
197
+ event.on(document, "keydown", (event2) => {
198
+ if (event2.key === "Escape") {
199
+ for (const tooltip of active) {
200
+ deactivate(tooltip, 0);
201
+ }
202
+ }
203
+ });