@oscarpalmer/oui 0.13.0 → 0.14.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,4 +1,13 @@
1
1
 
2
+ :where([oui-tooltip]:is(abbr, b, cite, dfn, code, em, i, kbd, mark, q, samp, small, span, strong, sub, sup, time, var)) {
3
+ -webkit-text-decoration: underline dotted;
4
+ text-decoration: underline dotted;
5
+ -webkit-text-decoration-skip: ink;
6
+ text-decoration-skip-ink: auto;
7
+ text-underline-offset: 0.25em;
8
+ cursor: help;
9
+ }
10
+
2
11
  :where([oui-tooltip-content]) {
3
12
  box-shadow: inset 0 1px 0 0 transparent, 0 0 0 1px rgba(0, 0, 0, 0.05), 0 1px 2px -0.5px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.05), 0 4px 8px -2px rgba(0, 0, 0, 0.05), 0 8px 16px -4px rgba(0, 0, 0, 0.05), 0 16px 32px -8px rgba(0, 0, 0, 0.05), 0 32px 64px -16px rgba(0, 0, 0, 0.05), 0 64px 128px -32px rgba(0, 0, 0, 0.05);
4
13
  max-width: 48ch;
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ class Floatable {
4
+ constructor(anchor, content) {
5
+ this.anchor = anchor;
6
+ this.content = content;
7
+ }
8
+ activate;
9
+ active = false;
10
+ deactivate;
11
+ frame;
12
+ destroy() {
13
+ this.activate = void 0;
14
+ this.deactivate = void 0;
15
+ this.frame = void 0;
16
+ this.anchor = void 0;
17
+ this.content = void 0;
18
+ }
19
+ }
20
+ function activate(floatable, active, time) {
21
+ stop(floatable);
22
+ if (floatable.active) {
23
+ return;
24
+ }
25
+ if (time == null || time === 0) {
26
+ onActivate(floatable, active);
27
+ } else {
28
+ floatable.activate = +setTimeout(() => {
29
+ onActivate(floatable, active);
30
+ }, time);
31
+ }
32
+ }
33
+ function deactivate(floatable, active, time) {
34
+ stop(floatable);
35
+ if (!floatable.active) {
36
+ return;
37
+ }
38
+ if (time == null || time === 0) {
39
+ onDeactivate(floatable, active);
40
+ } else {
41
+ floatable.deactivate = +setTimeout(() => {
42
+ onDeactivate(floatable, active);
43
+ }, time);
44
+ }
45
+ }
46
+ function getX(anchor, content) {
47
+ if (content.width + 2 * margin >= window.innerWidth) {
48
+ return {
49
+ left: margin,
50
+ right: margin
51
+ };
52
+ }
53
+ const left = anchor.left + (anchor.width - content.width) / 2;
54
+ const right = window.innerWidth - margin;
55
+ if (left < margin) {
56
+ return {
57
+ left: margin
58
+ };
59
+ }
60
+ if (left + content.width > right) {
61
+ return {
62
+ right: margin
63
+ };
64
+ }
65
+ return {
66
+ left
67
+ };
68
+ }
69
+ function getY(anchor, content) {
70
+ let top = anchor.top - content.height - margin;
71
+ if (top - margin >= 0) {
72
+ return top;
73
+ }
74
+ top = anchor.bottom + margin;
75
+ if (top + content.height + margin <= window.innerHeight) {
76
+ return top;
77
+ }
78
+ return anchor.top + (anchor.height - content.height) / 2;
79
+ }
80
+ function onActivate(floatable, active) {
81
+ floatable.active = true;
82
+ active.add(floatable);
83
+ document.body.append(floatable.content);
84
+ floatable.content.hidden = false;
85
+ update(floatable);
86
+ }
87
+ function onDeactivate(floatable, active) {
88
+ floatable.active = false;
89
+ active.delete(floatable);
90
+ floatable.content.hidden = true;
91
+ floatable.anchor.insertAdjacentElement("afterend", floatable.content);
92
+ }
93
+ function update(floatable) {
94
+ function run() {
95
+ const anchor = floatable.anchor.getBoundingClientRect();
96
+ const content = floatable.content.getBoundingClientRect();
97
+ const { left, right } = getX(anchor, content);
98
+ const top = getY(anchor, content);
99
+ floatable.content.style.inset = `${top}px ${right == null ? "auto" : `${right}px`} auto ${left == null ? "auto" : `${left}px`}`;
100
+ floatable.frame = requestAnimationFrame(run);
101
+ }
102
+ floatable.frame = requestAnimationFrame(run);
103
+ }
104
+ function stop(floatable) {
105
+ if (floatable.frame != null) {
106
+ cancelAnimationFrame(floatable.frame);
107
+ }
108
+ if (floatable.activate != null) {
109
+ clearTimeout(floatable.activate);
110
+ }
111
+ if (floatable.deactivate != null) {
112
+ clearTimeout(floatable.deactivate);
113
+ }
114
+ floatable.activate = void 0;
115
+ floatable.deactivate = void 0;
116
+ floatable.frame = void 0;
117
+ }
118
+ const margin = Number.parseInt(getComputedStyle(document.documentElement).fontSize, 10) / 4;
119
+ exports.Floatable = Floatable;
120
+ exports.activate = activate;
121
+ exports.deactivate = deactivate;
@@ -0,0 +1,121 @@
1
+ class Floatable {
2
+ constructor(anchor, content) {
3
+ this.anchor = anchor;
4
+ this.content = content;
5
+ }
6
+ activate;
7
+ active = false;
8
+ deactivate;
9
+ frame;
10
+ destroy() {
11
+ this.activate = void 0;
12
+ this.deactivate = void 0;
13
+ this.frame = void 0;
14
+ this.anchor = void 0;
15
+ this.content = void 0;
16
+ }
17
+ }
18
+ function activate(floatable, active, time) {
19
+ stop(floatable);
20
+ if (floatable.active) {
21
+ return;
22
+ }
23
+ if (time == null || time === 0) {
24
+ onActivate(floatable, active);
25
+ } else {
26
+ floatable.activate = +setTimeout(() => {
27
+ onActivate(floatable, active);
28
+ }, time);
29
+ }
30
+ }
31
+ function deactivate(floatable, active, time) {
32
+ stop(floatable);
33
+ if (!floatable.active) {
34
+ return;
35
+ }
36
+ if (time == null || time === 0) {
37
+ onDeactivate(floatable, active);
38
+ } else {
39
+ floatable.deactivate = +setTimeout(() => {
40
+ onDeactivate(floatable, active);
41
+ }, time);
42
+ }
43
+ }
44
+ function getX(anchor, content) {
45
+ if (content.width + 2 * margin >= window.innerWidth) {
46
+ return {
47
+ left: margin,
48
+ right: margin
49
+ };
50
+ }
51
+ const left = anchor.left + (anchor.width - content.width) / 2;
52
+ const right = window.innerWidth - margin;
53
+ if (left < margin) {
54
+ return {
55
+ left: margin
56
+ };
57
+ }
58
+ if (left + content.width > right) {
59
+ return {
60
+ right: margin
61
+ };
62
+ }
63
+ return {
64
+ left
65
+ };
66
+ }
67
+ function getY(anchor, content) {
68
+ let top = anchor.top - content.height - margin;
69
+ if (top - margin >= 0) {
70
+ return top;
71
+ }
72
+ top = anchor.bottom + margin;
73
+ if (top + content.height + margin <= window.innerHeight) {
74
+ return top;
75
+ }
76
+ return anchor.top + (anchor.height - content.height) / 2;
77
+ }
78
+ function onActivate(floatable, active) {
79
+ floatable.active = true;
80
+ active.add(floatable);
81
+ document.body.append(floatable.content);
82
+ floatable.content.hidden = false;
83
+ update(floatable);
84
+ }
85
+ function onDeactivate(floatable, active) {
86
+ floatable.active = false;
87
+ active.delete(floatable);
88
+ floatable.content.hidden = true;
89
+ floatable.anchor.insertAdjacentElement("afterend", floatable.content);
90
+ }
91
+ function update(floatable) {
92
+ function run() {
93
+ const anchor = floatable.anchor.getBoundingClientRect();
94
+ const content = floatable.content.getBoundingClientRect();
95
+ const { left, right } = getX(anchor, content);
96
+ const top = getY(anchor, content);
97
+ floatable.content.style.inset = `${top}px ${right == null ? "auto" : `${right}px`} auto ${left == null ? "auto" : `${left}px`}`;
98
+ floatable.frame = requestAnimationFrame(run);
99
+ }
100
+ floatable.frame = requestAnimationFrame(run);
101
+ }
102
+ function stop(floatable) {
103
+ if (floatable.frame != null) {
104
+ cancelAnimationFrame(floatable.frame);
105
+ }
106
+ if (floatable.activate != null) {
107
+ clearTimeout(floatable.activate);
108
+ }
109
+ if (floatable.deactivate != null) {
110
+ clearTimeout(floatable.deactivate);
111
+ }
112
+ floatable.activate = void 0;
113
+ floatable.deactivate = void 0;
114
+ floatable.frame = void 0;
115
+ }
116
+ const margin = Number.parseInt(getComputedStyle(document.documentElement).fontSize, 10) / 4;
117
+ export {
118
+ Floatable,
119
+ activate,
120
+ deactivate
121
+ };
@@ -2,144 +2,120 @@
2
2
  const is = require("@oscarpalmer/atoms/is");
3
3
  const event = require("@oscarpalmer/toretto/event");
4
4
  const attributable = require("./attributable.cjs");
5
+ const floatable = require("./floatable.cjs");
5
6
  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
7
  abortController = new AbortController();
15
- activate;
16
- active = false;
17
- deactivate;
18
- element;
19
- frame;
8
+ attributes;
9
+ floatable;
10
+ constructor(anchor, content) {
11
+ this.attributes = content.attributes;
12
+ this.floatable = new floatable.Floatable(anchor, content.element);
13
+ initialize(this);
14
+ }
20
15
  destroy() {
21
- deactivate(this);
16
+ floatable.deactivate(this.floatable, active);
17
+ reset(this.floatable.anchor, this.attributes);
22
18
  this.abortController.abort();
19
+ this.floatable.content.remove();
20
+ this.floatable.destroy();
23
21
  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;
22
+ this.attributes = void 0;
23
+ this.floatable = void 0;
29
24
  }
30
25
  }
31
- function activate(tooltip) {
32
- stop(tooltip);
33
- if (tooltip.active) {
26
+ function addTooltip(anchor) {
27
+ if (tooltips.has(anchor)) {
34
28
  return;
35
29
  }
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));
30
+ const content = getContent(anchor);
31
+ if (content != null) {
32
+ tooltips.set(anchor, new Tooltip(anchor, content));
47
33
  }
48
34
  }
49
- function deactivate(tooltip, time) {
50
- stop(tooltip);
51
- if (!tooltip.active) {
52
- return;
35
+ function getContent(anchor) {
36
+ const attributes = {
37
+ ariaDescribedby: anchor.getAttribute("aria-describedby"),
38
+ ariaLabelledBy: anchor.getAttribute("aria-labelledby")
39
+ };
40
+ const wrapper = getWrapper(anchor);
41
+ const labelledElements = getElements(attributes.ariaLabelledBy);
42
+ if (labelledElements.length > 0) {
43
+ anchor.setAttribute("aria-labelledby", wrapper.id);
44
+ for (const element of labelledElements) {
45
+ wrapper.append(element);
46
+ }
47
+ return {
48
+ attributes,
49
+ element: wrapper
50
+ };
53
51
  }
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;
52
+ const describedElements = getElements(attributes.ariaDescribedby);
53
+ if (describedElements.length > 0) {
54
+ anchor.setAttribute("aria-describedby", wrapper.id);
55
+ for (const element of describedElements) {
71
56
  wrapper.append(element);
72
57
  }
73
- return wrapper;
58
+ return {
59
+ attributes,
60
+ element: wrapper
61
+ };
74
62
  }
75
- let content = parent.getAttribute("aria-description");
63
+ let content = anchor.getAttribute("aria-label");
76
64
  if (is.isNullableOrWhitespace(content)) {
77
- content = parent.getAttribute("aria-label");
78
- if (is.isNullableOrWhitespace(content)) {
79
- return;
80
- }
65
+ content = anchor.getAttribute("aria-description");
66
+ }
67
+ if (is.isNullableOrWhitespace(content)) {
68
+ return;
81
69
  }
82
70
  const paragraph = document.createElement("p");
83
71
  paragraph.textContent = content;
84
72
  wrapper.append(paragraph);
85
- return wrapper;
73
+ return {
74
+ attributes,
75
+ element: wrapper
76
+ };
86
77
  }
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;
78
+ function getElements(attribute) {
79
+ const ids = attribute?.split(/\s+/) ?? [];
80
+ const elements = [];
81
+ for (const id of ids) {
82
+ if (is.isNullableOrWhitespace(id)) {
83
+ continue;
84
+ }
85
+ const element = document.querySelector(`#${id}`);
86
+ if (element instanceof HTMLElement) {
87
+ const cloned = element.cloneNode(true);
88
+ cloned.hidden = false;
89
+ elements.push(cloned);
90
+ }
95
91
  }
96
- return parent.top + (parent.height - content.height) / 2;
92
+ return elements;
97
93
  }
98
- function getWrapper(parent) {
94
+ function getWrapper(anchor) {
99
95
  const wrapper = document.createElement("div");
100
96
  wrapper.setAttribute(`${selector}-content`, "");
101
- wrapper.className = parent.getAttribute(`${selector}-class`) ?? "";
97
+ wrapper.className = anchor.getAttribute(`${selector}-class`) ?? "";
102
98
  wrapper.hidden = true;
103
99
  wrapper.id = `${selector}-${++index}`;
104
100
  wrapper.role = "tooltip";
105
101
  wrapper.tabIndex = -1;
106
102
  return wrapper;
107
103
  }
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
104
  function initialize(tooltip) {
132
- const elements = [tooltip.parent, tooltip.element];
105
+ const { abortController, floatable: floatable$1 } = tooltip;
106
+ const { anchor, content } = floatable$1;
107
+ anchor.insertAdjacentElement("afterend", content);
108
+ const elements = [anchor, content];
133
109
  for (const element of elements) {
134
110
  for (const event$1 of activateEvents) {
135
111
  event.on(
136
112
  element,
137
113
  event$1,
138
114
  () => {
139
- activate(tooltip);
115
+ floatable.activate(floatable$1, active, 250);
140
116
  },
141
117
  {
142
- signal: tooltip.abortController.signal
118
+ signal: abortController.signal
143
119
  }
144
120
  );
145
121
  }
@@ -148,10 +124,10 @@ function initialize(tooltip) {
148
124
  element,
149
125
  event$1,
150
126
  () => {
151
- deactivate(tooltip);
127
+ floatable.deactivate(floatable$1, active, 250);
152
128
  },
153
129
  {
154
- signal: tooltip.abortController.signal
130
+ signal: abortController.signal
155
131
  }
156
132
  );
157
133
  }
@@ -161,43 +137,25 @@ function removeTooltip(element) {
161
137
  tooltips.get(element)?.destroy();
162
138
  tooltips.delete(element);
163
139
  }
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);
140
+ function reset(anchor, attributes) {
141
+ console.log("reset", anchor, attributes);
142
+ if (attributes.ariaDescribedby == null) {
143
+ anchor.removeAttribute("aria-describedby");
144
+ } else {
145
+ anchor.setAttribute("aria-describedby", attributes.ariaDescribedby);
186
146
  }
187
- tooltip.frame = requestAnimationFrame(run);
188
147
  }
189
148
  const active = /* @__PURE__ */ new Set();
190
149
  const activateEvents = ["focus", "mouseenter", "touchstart"];
191
150
  const deactivateEvents = ["blur", "mouseleave"];
192
- const margin = Number.parseInt(getComputedStyle(document.documentElement).fontSize, 10) / 4;
193
151
  const selector = "oui-tooltip";
194
152
  const tooltips = /* @__PURE__ */ new WeakMap();
195
153
  let index = 0;
196
154
  attributable.attributable(selector, addTooltip, removeTooltip);
197
155
  event.on(document, "keydown", (event2) => {
198
156
  if (event2.key === "Escape") {
199
- for (const tooltip of active) {
200
- deactivate(tooltip, 0);
157
+ for (const floatable$1 of active) {
158
+ floatable.deactivate(floatable$1, active);
201
159
  }
202
160
  }
203
161
  });