@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.
@@ -5,5 +5,8 @@
5
5
  background-color: var(--oui-absolute);
6
6
  border-radius: 0.5rem;
7
7
  }
8
+ :where([oui-popover-content]):focus {
9
+ outline: none;
10
+ }
8
11
 
9
12
 
@@ -22,7 +22,7 @@
22
22
  :where([oui-splitter-separator]:has([oui-splitter-handle]:focus)),
23
23
  :where([oui-splitter-separator]:has([oui-splitter-handle]:hover)),
24
24
  :where([oui-splitter-separator][oui-splitter-active]) {
25
- background-color: rgba(0, 0, 255, 0.1);
25
+ background-color: color-mix(in oklab, var(--oui-blue-6), transparent 82.5%);
26
26
  }
27
27
 
28
28
  :where([oui-splitter-handle]) {
@@ -52,11 +52,11 @@
52
52
  :where([oui-splitter-handle]:focus)::after,
53
53
  :where([oui-splitter-handle]:hover)::after,
54
54
  :where([oui-splitter-handle][oui-splitter-active])::after {
55
- background-color: rgba(0, 0, 255, 0.25);
55
+ background-color: color-mix(in oklab, var(--oui-blue-6), transparent 50%);
56
56
  }
57
57
 
58
58
  :where([oui-splitter-handle]:focus-visible)::after {
59
- outline-color: rgba(0, 0, 255, 0.25);
59
+ outline-color: color-mix(in oklab, var(--oui-blue-6), transparent 50%);
60
60
  }
61
61
 
62
62
  :where(oui-splitter[type=horizontal i],
@@ -1,9 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  class Floatable {
4
- constructor(anchor, content) {
4
+ constructor(anchor, content, attribute, position, preferAbove) {
5
5
  this.anchor = anchor;
6
6
  this.content = content;
7
+ this.attribute = attribute;
8
+ this.position = position;
9
+ this.preferAbove = preferAbove;
7
10
  setStyles(content);
8
11
  }
9
12
  activate;
@@ -44,37 +47,52 @@ function deactivate(floatable, active, order, time) {
44
47
  }, time);
45
48
  }
46
49
  }
47
- function getX(anchor, content) {
48
- if (content.width + 2 * margin >= window.innerWidth) {
49
- return {
50
- left: margin,
51
- right: margin
52
- };
53
- }
54
- const left = anchor.left + (anchor.width - content.width) / 2;
55
- const right = window.innerWidth - margin;
56
- if (left < margin) {
57
- return {
58
- left: margin
59
- };
60
- }
61
- if (left + content.width > right) {
62
- return {
63
- right: margin
64
- };
65
- }
66
- return {
67
- left
68
- };
69
- }
70
- function getY(anchor, content) {
71
- let top = anchor.top - content.height - margin;
72
- if (top - margin >= 0) {
73
- return top;
50
+ function getX(anchor, content, position) {
51
+ if (position.endsWith("-end")) {
52
+ const end = anchor.right - content.width;
53
+ return end - margin < 0 ? margin : end;
54
+ }
55
+ if (position.endsWith("-start")) {
56
+ const start = anchor.left;
57
+ return start + content.width + margin > window.innerWidth ? window.innerWidth - content.width - margin : start;
58
+ }
59
+ if (position.startsWith("end")) {
60
+ return anchor.right + margin;
61
+ }
62
+ if (position.startsWith("start")) {
63
+ return anchor.left - content.width - margin;
74
64
  }
75
- top = anchor.bottom + margin;
76
- if (top + content.height + margin <= window.innerHeight) {
77
- return top;
65
+ if (position.startsWith("horizontal")) {
66
+ if (anchor.right + content.width + margin <= window.innerWidth) {
67
+ return anchor.right + margin;
68
+ }
69
+ return anchor.left - content.width - margin;
70
+ }
71
+ return anchor.left + (anchor.width - content.width) / 2;
72
+ }
73
+ function getY(anchor, content, position, preferAbove) {
74
+ if (position.startsWith("above")) {
75
+ return anchor.top - content.height - margin;
76
+ }
77
+ if (position.startsWith("below")) {
78
+ return anchor.bottom + margin;
79
+ }
80
+ if (position.endsWith("bottom")) {
81
+ const bottom = anchor.bottom - content.height;
82
+ return bottom + margin < 0 ? margin : bottom;
83
+ }
84
+ if (position.endsWith("top")) {
85
+ const top = anchor.top;
86
+ return top + content.height + margin > window.innerHeight ? window.innerHeight - content.height - margin : top;
87
+ }
88
+ if (position.startsWith("vertical")) {
89
+ if (preferAbove && anchor.top - content.height - margin >= 0) {
90
+ return anchor.top - content.height - margin;
91
+ }
92
+ if (anchor.bottom + content.height + margin <= document.body.clientHeight) {
93
+ return anchor.bottom + margin;
94
+ }
95
+ return anchor.top - content.height - margin;
78
96
  }
79
97
  return anchor.top + (anchor.height - content.height) / 2;
80
98
  }
@@ -99,12 +117,16 @@ function onDeactivate(floatable, active, order) {
99
117
  floatable.anchor.insertAdjacentElement("afterend", floatable.content);
100
118
  }
101
119
  function update(floatable) {
120
+ let position = floatable.anchor.getAttribute(floatable.attribute)?.trim() ?? floatable.position;
121
+ if (!positions.has(position)) {
122
+ position = floatable.position;
123
+ }
102
124
  function run() {
103
125
  const anchor = floatable.anchor.getBoundingClientRect();
104
126
  const content = floatable.content.getBoundingClientRect();
105
- const { left, right } = getX(anchor, content);
106
- const top = getY(anchor, content);
107
- floatable.content.style.inset = `${top}px ${right == null ? "auto" : `${right}px`} auto ${left == null ? "auto" : `${left}px`}`;
127
+ const left = getX(anchor, content, position);
128
+ const top = getY(anchor, content, position, floatable.preferAbove);
129
+ floatable.content.style.inset = `${top}px auto auto ${left}px`;
108
130
  floatable.frame = requestAnimationFrame(run);
109
131
  }
110
132
  floatable.frame = requestAnimationFrame(run);
@@ -129,6 +151,26 @@ function stop(floatable) {
129
151
  floatable.frame = void 0;
130
152
  }
131
153
  const margin = Number.parseInt(getComputedStyle(document.documentElement).fontSize, 10) / 2;
154
+ const positions = /* @__PURE__ */ new Set([
155
+ "above",
156
+ "above-end",
157
+ "above-start",
158
+ "below",
159
+ "below-end",
160
+ "below-start",
161
+ "end",
162
+ "end-bottom",
163
+ "end-top",
164
+ "horizontal",
165
+ "horizontal-bottom",
166
+ "horizontal-top",
167
+ "start",
168
+ "start-bottom",
169
+ "start-top",
170
+ "vertical",
171
+ "vertical-end",
172
+ "vertical-start"
173
+ ]);
132
174
  exports.Floatable = Floatable;
133
175
  exports.activate = activate;
134
176
  exports.deactivate = deactivate;
@@ -1,7 +1,10 @@
1
1
  class Floatable {
2
- constructor(anchor, content) {
2
+ constructor(anchor, content, attribute, position, preferAbove) {
3
3
  this.anchor = anchor;
4
4
  this.content = content;
5
+ this.attribute = attribute;
6
+ this.position = position;
7
+ this.preferAbove = preferAbove;
5
8
  setStyles(content);
6
9
  }
7
10
  activate;
@@ -42,37 +45,52 @@ function deactivate(floatable, active, order, time) {
42
45
  }, time);
43
46
  }
44
47
  }
45
- function getX(anchor, content) {
46
- if (content.width + 2 * margin >= window.innerWidth) {
47
- return {
48
- left: margin,
49
- right: margin
50
- };
51
- }
52
- const left = anchor.left + (anchor.width - content.width) / 2;
53
- const right = window.innerWidth - margin;
54
- if (left < margin) {
55
- return {
56
- left: margin
57
- };
58
- }
59
- if (left + content.width > right) {
60
- return {
61
- right: margin
62
- };
63
- }
64
- return {
65
- left
66
- };
67
- }
68
- function getY(anchor, content) {
69
- let top = anchor.top - content.height - margin;
70
- if (top - margin >= 0) {
71
- return top;
48
+ function getX(anchor, content, position) {
49
+ if (position.endsWith("-end")) {
50
+ const end = anchor.right - content.width;
51
+ return end - margin < 0 ? margin : end;
52
+ }
53
+ if (position.endsWith("-start")) {
54
+ const start = anchor.left;
55
+ return start + content.width + margin > window.innerWidth ? window.innerWidth - content.width - margin : start;
56
+ }
57
+ if (position.startsWith("end")) {
58
+ return anchor.right + margin;
59
+ }
60
+ if (position.startsWith("start")) {
61
+ return anchor.left - content.width - margin;
72
62
  }
73
- top = anchor.bottom + margin;
74
- if (top + content.height + margin <= window.innerHeight) {
75
- return top;
63
+ if (position.startsWith("horizontal")) {
64
+ if (anchor.right + content.width + margin <= window.innerWidth) {
65
+ return anchor.right + margin;
66
+ }
67
+ return anchor.left - content.width - margin;
68
+ }
69
+ return anchor.left + (anchor.width - content.width) / 2;
70
+ }
71
+ function getY(anchor, content, position, preferAbove) {
72
+ if (position.startsWith("above")) {
73
+ return anchor.top - content.height - margin;
74
+ }
75
+ if (position.startsWith("below")) {
76
+ return anchor.bottom + margin;
77
+ }
78
+ if (position.endsWith("bottom")) {
79
+ const bottom = anchor.bottom - content.height;
80
+ return bottom + margin < 0 ? margin : bottom;
81
+ }
82
+ if (position.endsWith("top")) {
83
+ const top = anchor.top;
84
+ return top + content.height + margin > window.innerHeight ? window.innerHeight - content.height - margin : top;
85
+ }
86
+ if (position.startsWith("vertical")) {
87
+ if (preferAbove && anchor.top - content.height - margin >= 0) {
88
+ return anchor.top - content.height - margin;
89
+ }
90
+ if (anchor.bottom + content.height + margin <= document.body.clientHeight) {
91
+ return anchor.bottom + margin;
92
+ }
93
+ return anchor.top - content.height - margin;
76
94
  }
77
95
  return anchor.top + (anchor.height - content.height) / 2;
78
96
  }
@@ -97,12 +115,16 @@ function onDeactivate(floatable, active, order) {
97
115
  floatable.anchor.insertAdjacentElement("afterend", floatable.content);
98
116
  }
99
117
  function update(floatable) {
118
+ let position = floatable.anchor.getAttribute(floatable.attribute)?.trim() ?? floatable.position;
119
+ if (!positions.has(position)) {
120
+ position = floatable.position;
121
+ }
100
122
  function run() {
101
123
  const anchor = floatable.anchor.getBoundingClientRect();
102
124
  const content = floatable.content.getBoundingClientRect();
103
- const { left, right } = getX(anchor, content);
104
- const top = getY(anchor, content);
105
- floatable.content.style.inset = `${top}px ${right == null ? "auto" : `${right}px`} auto ${left == null ? "auto" : `${left}px`}`;
125
+ const left = getX(anchor, content, position);
126
+ const top = getY(anchor, content, position, floatable.preferAbove);
127
+ floatable.content.style.inset = `${top}px auto auto ${left}px`;
106
128
  floatable.frame = requestAnimationFrame(run);
107
129
  }
108
130
  floatable.frame = requestAnimationFrame(run);
@@ -127,6 +149,26 @@ function stop(floatable) {
127
149
  floatable.frame = void 0;
128
150
  }
129
151
  const margin = Number.parseInt(getComputedStyle(document.documentElement).fontSize, 10) / 2;
152
+ const positions = /* @__PURE__ */ new Set([
153
+ "above",
154
+ "above-end",
155
+ "above-start",
156
+ "below",
157
+ "below-end",
158
+ "below-start",
159
+ "end",
160
+ "end-bottom",
161
+ "end-top",
162
+ "horizontal",
163
+ "horizontal-bottom",
164
+ "horizontal-top",
165
+ "start",
166
+ "start-bottom",
167
+ "start-top",
168
+ "vertical",
169
+ "vertical-end",
170
+ "vertical-start"
171
+ ]);
130
172
  export {
131
173
  Floatable,
132
174
  activate,
@@ -34,7 +34,13 @@ class OuiPopoverElement extends HTMLElement {
34
34
  }
35
35
  toggle2.setAttribute("aria-controls", content.id);
36
36
  toggle2.setAttribute("aria-haspopup", "dialog");
37
- this.#floatable = new floatable.Floatable(toggle2, content);
37
+ this.#floatable = new floatable.Floatable(
38
+ toggle2,
39
+ content,
40
+ "position",
41
+ "below-start",
42
+ false
43
+ );
38
44
  this.#focusTrap = focusTrap_embedded.createFocusTrap(content);
39
45
  mapped.set(toggle2, this.#floatable);
40
46
  mapped.set(content, this.#floatable);
@@ -32,7 +32,13 @@ class OuiPopoverElement extends HTMLElement {
32
32
  }
33
33
  toggle2.setAttribute("aria-controls", content.id);
34
34
  toggle2.setAttribute("aria-haspopup", "dialog");
35
- this.#floatable = new Floatable(toggle2, content);
35
+ this.#floatable = new Floatable(
36
+ toggle2,
37
+ content,
38
+ "position",
39
+ "below-start",
40
+ false
41
+ );
36
42
  this.#focusTrap = createFocusTrap(content);
37
43
  mapped.set(toggle2, this.#floatable);
38
44
  mapped.set(content, this.#floatable);
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const is = require("@oscarpalmer/atoms/is");
4
+ const math = require("@oscarpalmer/atoms/math");
3
5
  const number = require("@oscarpalmer/atoms/number");
4
6
  const event = require("@oscarpalmer/toretto/event");
5
7
  const find = require("@oscarpalmer/toretto/find");
@@ -26,49 +28,59 @@ class OuiSplitterElement extends HTMLElement {
26
28
  }
27
29
  }
28
30
  }
31
+ let index = 0;
29
32
  class Splitter {
30
33
  constructor(element, panels) {
31
34
  this.element = element;
32
35
  this.panels = panels;
36
+ if (is.isNullableOrWhitespace(element.id)) {
37
+ element.id = `oui_splitter_${++index}`;
38
+ }
39
+ this.values = getValues(element);
33
40
  this.handle = createHandle();
34
41
  this.rectangle = element.getBoundingClientRect();
35
- this.separator = createSeparator();
42
+ this.separator = createSeparator(this);
36
43
  this.separator.append(this.handle);
37
44
  panels[0].insertAdjacentElement("afterend", this.separator);
38
45
  mapped.set(element, this);
39
46
  mapped.set(this.handle, this);
40
- setSize(this, this.flex.value);
47
+ setSize(this, this.values.now.current);
41
48
  this.types = getTypes(this);
42
49
  if (this.types.horizontal) {
43
50
  this.element.setAttribute("oui-splitter-horizontal", "");
44
51
  }
52
+ setAriaOrientation(this);
53
+ setAriaValue(this);
45
54
  }
46
- flex = {
47
- previous: 0.5,
48
- value: 0.5
49
- };
50
55
  handle;
51
56
  rectangle;
52
57
  separator;
53
58
  types;
59
+ values;
54
60
  }
55
61
  function createHandle() {
56
62
  const handle = document.createElement("span");
57
- handle.tabIndex = 0;
58
- handle.setAttribute("aria-label", "Resize panels");
63
+ handle.setAttribute("aria-hidden", "true");
59
64
  handle.setAttribute("oui-splitter-handle", "");
60
65
  return handle;
61
66
  }
62
- function createSeparator() {
67
+ function createSeparator(splitter2) {
63
68
  const separator = document.createElement("div");
69
+ separator.role = "separator";
70
+ separator.tabIndex = 0;
71
+ separator.setAttribute("aria-controls", splitter2.element.id);
72
+ separator.setAttribute("aria-label", "Resize panels");
64
73
  separator.setAttribute("oui-splitter-separator", "");
65
74
  return separator;
66
75
  }
67
- function getFlex(splitter2, value) {
76
+ function getContained(min, value, max) {
77
+ return math.round(number.clamp(value, min, max), 6);
78
+ }
79
+ function getSize(splitter2, value) {
68
80
  const { height, left, top, width } = splitter2.rectangle;
69
81
  const offset = value - (splitter2.types.horizontal ? top : left);
70
82
  const fraction = offset / (splitter2.types.horizontal ? height : width);
71
- return number.clamp(fraction, 0.1, 0.9);
83
+ return getContained(splitter2.values.min, fraction, splitter2.values.max);
72
84
  }
73
85
  function getTypes(splitter2, width, height) {
74
86
  const type = splitter2.element.getAttribute("type")?.trim() ?? "";
@@ -89,14 +101,43 @@ function getTypes(splitter2, width, height) {
89
101
  horizontal: false
90
102
  };
91
103
  }
104
+ function getValue(element, name, defaultValue) {
105
+ const value = element.getAttribute(name);
106
+ if (is.isNullableOrWhitespace(value)) {
107
+ return defaultValue;
108
+ }
109
+ const parsed = Number.parseFloat(value);
110
+ if (Number.isNaN(parsed)) {
111
+ return defaultValue;
112
+ }
113
+ return parsed < 0 ? defaultValue : parsed > 1 ? parsed / 100 : parsed;
114
+ }
115
+ function getValues(element) {
116
+ const values = {
117
+ max: getValue(element, "max", 0.9),
118
+ min: getValue(element, "min", 0.1),
119
+ now: {
120
+ current: 0.5,
121
+ previous: 0.5
122
+ }
123
+ };
124
+ const value = getContained(
125
+ values.min,
126
+ getValue(element, "size", values.now.current),
127
+ values.max
128
+ );
129
+ values.now.current = value;
130
+ values.now.previous = value;
131
+ return values;
132
+ }
92
133
  function onEnd(reset) {
93
134
  if (splitter != null) {
94
135
  splitter.handle.removeAttribute("oui-splitter-active");
95
136
  splitter.separator.removeAttribute("oui-splitter-active");
96
137
  if (reset) {
97
- setSize(splitter, splitter.flex.previous);
138
+ setSize(splitter, splitter.values.now.previous);
98
139
  } else {
99
- splitter.flex.previous = splitter.flex.value;
140
+ splitter.values.now.previous = splitter.values.now.current;
100
141
  }
101
142
  }
102
143
  splitter = void 0;
@@ -131,11 +172,11 @@ function onMousemove(event$1) {
131
172
  }
132
173
  const position = event.getPosition(event$1);
133
174
  if (position != null) {
134
- splitter.flex.value = getFlex(
175
+ splitter.values.now.current = getSize(
135
176
  splitter,
136
177
  splitter.types.horizontal ? position.y : position.x
137
178
  );
138
- setSize(splitter, splitter.flex.value);
179
+ setSize(splitter, splitter.values.now.current);
139
180
  }
140
181
  }
141
182
  function onMouseup() {
@@ -154,14 +195,18 @@ function onNavigate(event2) {
154
195
  return;
155
196
  }
156
197
  if (absoluteKeys.has(event2.key)) {
157
- setSize(splitter2, event2.key === "End" ? 0.9 : 0.1, true);
198
+ setSize(
199
+ splitter2,
200
+ event2.key === "End" ? splitter2.values.max : splitter2.values.min,
201
+ true
202
+ );
158
203
  return;
159
204
  }
160
205
  if (splitter2.types.horizontal && !horizontalKeys.has(event2.key) || !splitter2.types.horizontal && !verticalKeys.has(event2.key)) {
161
206
  return;
162
207
  }
163
208
  const modifier = negativeKeys.has(event2.key) ? -1 : 1;
164
- setSize(splitter2, splitter2.flex.value + modifier * 0.05, true);
209
+ setSize(splitter2, splitter2.values.now.current + modifier * 0.05, true);
165
210
  }
166
211
  function onObservation(entries) {
167
212
  if (frame != null) {
@@ -183,16 +228,32 @@ function onObservation(entries) {
183
228
  } else {
184
229
  splitter2.element.removeAttribute("oui-splitter-horizontal");
185
230
  }
231
+ setAriaOrientation(splitter2);
186
232
  }
187
233
  frame = void 0;
188
234
  });
189
235
  }
236
+ function setAriaOrientation(splitter2) {
237
+ splitter2.separator.setAttribute(
238
+ "aria-orientation",
239
+ splitter2.types.horizontal ? "horizontal" : "vertical"
240
+ );
241
+ }
242
+ function setAriaValue(splitter2) {
243
+ splitter2.separator.setAttribute("aria-valuemax", String(splitter2.values.max));
244
+ splitter2.separator.setAttribute("aria-valuemin", String(splitter2.values.min));
245
+ splitter2.separator.setAttribute(
246
+ "aria-valuenow",
247
+ String(splitter2.values.now.current)
248
+ );
249
+ }
190
250
  function setSize(splitter2, size, previous) {
191
- splitter2.flex.value = size;
251
+ splitter2.values.now.current = size;
192
252
  if (previous) {
193
- splitter2.flex.previous = size;
253
+ splitter2.values.now.previous = size;
194
254
  }
195
255
  splitter2.element.style.setProperty("--oui-splitter-size", `${size}`);
256
+ setAriaValue(splitter2);
196
257
  }
197
258
  const absoluteKeys = /* @__PURE__ */ new Set(["End", "Home"]);
198
259
  const horizontalKeys = /* @__PURE__ */ new Set(["ArrowDown", "ArrowUp"]);