@oscarpalmer/oui 0.14.0 → 0.16.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 (46) hide show
  1. package/README.md +2 -0
  2. package/dist/css/button.css +0 -3
  3. package/dist/css/popover.css +9 -0
  4. package/dist/css/splitter.css +78 -0
  5. package/dist/css/tooltip.css +0 -3
  6. package/dist/js/floatable.cjs +22 -9
  7. package/dist/js/floatable.js +22 -9
  8. package/dist/js/focus-trap/embedded.cjs +150 -0
  9. package/dist/js/focus-trap/embedded.js +150 -0
  10. package/dist/js/focus-trap/standalone.cjs +8 -0
  11. package/dist/js/focus-trap/standalone.js +7 -0
  12. package/dist/js/index.cjs +5 -1
  13. package/dist/js/index.js +6 -1
  14. package/dist/js/popover.cjs +160 -0
  15. package/dist/js/popover.js +160 -0
  16. package/dist/js/splitter.cjs +211 -0
  17. package/dist/js/splitter.js +211 -0
  18. package/dist/js/tooltip.cjs +2 -3
  19. package/dist/js/tooltip.js +2 -3
  20. package/package.json +25 -5
  21. package/src/css/button.scss +0 -3
  22. package/src/css/popover.scss +9 -0
  23. package/src/css/splitter.scss +87 -0
  24. package/src/css/tooltip.scss +0 -3
  25. package/src/js/floatable.ts +53 -25
  26. package/src/js/focus-trap/embedded.ts +215 -0
  27. package/src/js/focus-trap/standalone.ts +9 -0
  28. package/src/js/index.ts +3 -1
  29. package/src/js/popover.ts +221 -0
  30. package/src/js/splitter.ts +317 -0
  31. package/src/js/tooltip.ts +2 -4
  32. package/types/floatable.d.cts +2 -2
  33. package/types/floatable.d.ts +2 -2
  34. package/types/focus-trap/embedded.d.cts +15 -0
  35. package/types/focus-trap/embedded.d.ts +11 -0
  36. package/types/index.d.cts +11 -0
  37. package/types/index.d.ts +3 -1
  38. package/types/popover.d.cts +14 -0
  39. package/types/popover.d.ts +15 -0
  40. package/types/splitter.d.cts +10 -0
  41. package/types/splitter.d.ts +11 -0
  42. package/dist/js/focus-trap.cjs +0 -104
  43. package/dist/js/focus-trap.js +0 -103
  44. package/src/js/focus-trap.ts +0 -133
  45. /package/types/{focus-trap.d.cts → focus-trap/standalone.d.cts} +0 -0
  46. /package/types/{focus-trap.d.ts → focus-trap/standalone.d.ts} +0 -0
@@ -0,0 +1,211 @@
1
+ import { clamp } from "@oscarpalmer/atoms/number";
2
+ import { on, getPosition } from "@oscarpalmer/toretto/event";
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
+ }
53
+ 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
+ };
89
+ }
90
+ 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;
101
+ }
102
+ 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
+ }
143
+ }
144
+ 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);
163
+ }
164
+ 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);
205
+ on(document, "keydown", onKeydown);
206
+ on(document, "mousedown", onMousedown);
207
+ on(document, "mousemove", onMousemove);
208
+ on(document, "mouseup", onMouseup);
209
+ export {
210
+ OuiSplitterElement
211
+ };
@@ -112,7 +112,7 @@ function initialize(tooltip) {
112
112
  element,
113
113
  event$1,
114
114
  () => {
115
- floatable.activate(floatable$1, active, 250);
115
+ floatable.activate(floatable$1, active, void 0, 250);
116
116
  },
117
117
  {
118
118
  signal: abortController.signal
@@ -124,7 +124,7 @@ function initialize(tooltip) {
124
124
  element,
125
125
  event$1,
126
126
  () => {
127
- floatable.deactivate(floatable$1, active, 250);
127
+ floatable.deactivate(floatable$1, active, void 0, 250);
128
128
  },
129
129
  {
130
130
  signal: abortController.signal
@@ -138,7 +138,6 @@ function removeTooltip(element) {
138
138
  tooltips.delete(element);
139
139
  }
140
140
  function reset(anchor, attributes) {
141
- console.log("reset", anchor, attributes);
142
141
  if (attributes.ariaDescribedby == null) {
143
142
  anchor.removeAttribute("aria-describedby");
144
143
  } else {
@@ -111,7 +111,7 @@ function initialize(tooltip) {
111
111
  element,
112
112
  event,
113
113
  () => {
114
- activate(floatable, active, 250);
114
+ activate(floatable, active, void 0, 250);
115
115
  },
116
116
  {
117
117
  signal: abortController.signal
@@ -123,7 +123,7 @@ function initialize(tooltip) {
123
123
  element,
124
124
  event,
125
125
  () => {
126
- deactivate(floatable, active, 250);
126
+ deactivate(floatable, active, void 0, 250);
127
127
  },
128
128
  {
129
129
  signal: abortController.signal
@@ -137,7 +137,6 @@ function removeTooltip(element) {
137
137
  tooltips.delete(element);
138
138
  }
139
139
  function reset(anchor, attributes) {
140
- console.log("reset", anchor, attributes);
141
140
  if (attributes.ariaDescribedby == null) {
142
141
  anchor.removeAttribute("aria-describedby");
143
142
  } else {
package/package.json CHANGED
@@ -41,12 +41,32 @@
41
41
  },
42
42
  "./focus-trap": {
43
43
  "import": {
44
- "types": "./types/focus-trap.d.ts",
45
- "default": "./dist/js/focus-trap.js"
44
+ "types": "./types/focus-trap/standalone.d.ts",
45
+ "default": "./dist/js/focus-trap/standalone.js"
46
46
  },
47
47
  "require": {
48
- "types": "./types/focus-trap.d.cts",
49
- "default": "./dist/js/focus-trap.cjs"
48
+ "types": "./types/focus-trap/standalone.d.cts",
49
+ "default": "./dist/js/focus-trap/standalone.cjs"
50
+ }
51
+ },
52
+ "./popover": {
53
+ "import": {
54
+ "types": "./types/popover.d.ts",
55
+ "default": "./dist/js/popover.js"
56
+ },
57
+ "require": {
58
+ "types": "./types/popover.d.cts",
59
+ "default": "./dist/js/popover.cjs"
60
+ }
61
+ },
62
+ "./splitter": {
63
+ "import": {
64
+ "types": "./types/splitter.d.ts",
65
+ "default": "./dist/js/splitter.js"
66
+ },
67
+ "require": {
68
+ "types": "./types/splitter.d.cts",
69
+ "default": "./dist/js/splitter.cjs"
50
70
  }
51
71
  },
52
72
  "./tooltip": {
@@ -94,5 +114,5 @@
94
114
  },
95
115
  "type": "module",
96
116
  "types": "types/index.d.cts",
97
- "version": "0.14.0"
117
+ "version": "0.16.0"
98
118
  }
@@ -58,9 +58,6 @@
58
58
  --oui-foreground-percentage: 12.5%;
59
59
 
60
60
  font-size: 0.75em;
61
- font-weight: bold;
62
- letter-spacing: 0.1em;
63
- text-transform: uppercase;
64
61
  }
65
62
 
66
63
  :where(&:focus-visible) {
@@ -0,0 +1,9 @@
1
+ @use 'mixins.scss' as mixins;
2
+
3
+ :where([oui-popover-content]) {
4
+ @include mixins.shadowOverlay;
5
+
6
+ padding: 1rem;
7
+ background-color: var(--oui-absolute);
8
+ border-radius: 0.5rem;
9
+ }
@@ -0,0 +1,87 @@
1
+ @use 'mixins.scss' as mixins;
2
+
3
+ oui-splitter {
4
+ :where(&) {
5
+ display: flex;
6
+ flex-flow: row nowrap;
7
+ flex: 1;
8
+ }
9
+
10
+ :where(& > :first-child) {
11
+ flex: var(--oui-splitter-size, .5);
12
+ }
13
+
14
+ :where(& > :last-child) {
15
+ flex: calc(1 - var(--oui-splitter-size, .5));
16
+ }
17
+ }
18
+
19
+ :where([oui-splitter-separator]) {
20
+ width: .25rem;
21
+ background-color: rgba(0, 0, 0, 0.1);
22
+ border-radius: .25rem;
23
+ position: relative;
24
+ transition: background .125s ease-in-out;
25
+ }
26
+
27
+ :where([oui-splitter-separator]:has([oui-splitter-handle]:focus)),
28
+ :where([oui-splitter-separator]:has([oui-splitter-handle]:hover)),
29
+ :where([oui-splitter-separator][oui-splitter-active]) {
30
+ background-color: rgba(0, 0, 255, .1);
31
+ }
32
+
33
+ :where([oui-splitter-handle]) {
34
+ width: 2rem;
35
+ height: 4rem;
36
+ padding: .875rem;
37
+ position: absolute;
38
+ inset: 50% auto auto 50%;
39
+ transform: translate3d(-50%, -50%, 0);
40
+ cursor: ew-resize;
41
+
42
+ &:focus {
43
+ outline: none;
44
+ }
45
+
46
+ &::after {
47
+ content: '';
48
+ width: 100%;
49
+ height: 100%;
50
+ display: block;
51
+ background-color: rgba(0, 0, 0, 0.25);
52
+ outline: .25rem solid transparent;
53
+ outline-offset: .25rem;
54
+ border-radius: .25rem;
55
+ transition: background .125s ease-in-out, outline .125s ease-in-out;
56
+ }
57
+ }
58
+
59
+ :where([oui-splitter-handle]:focus),
60
+ :where([oui-splitter-handle]:hover),
61
+ :where([oui-splitter-handle][oui-splitter-active]) {
62
+ &::after {
63
+ background-color: rgba(0, 0, 255, .25);
64
+ }
65
+ }
66
+
67
+ :where([oui-splitter-handle]:focus-visible)::after {
68
+ outline-color: rgba(0, 0, 255, .25);
69
+ }
70
+
71
+ oui-splitter[type='horizontal' i],
72
+ oui-splitter[oui-splitter-horizontal] {
73
+ :where(&) {
74
+ flex-flow: column nowrap;
75
+ }
76
+
77
+ :where(& [oui-splitter-separator]) {
78
+ width: 100%;
79
+ height: .25rem;
80
+ }
81
+
82
+ :where(& [oui-splitter-handle]) {
83
+ width: 4rem;
84
+ height: 2rem;
85
+ cursor: ns-resize;
86
+ }
87
+ }
@@ -15,7 +15,4 @@
15
15
  background-color: var(--oui-absolute);
16
16
  border-radius: 0.25rem;
17
17
  font-size: 0.875rem;
18
- position: fixed;
19
- inset: 0 auto auto 0;
20
- z-index: 10;
21
18
  }
@@ -1,27 +1,30 @@
1
1
  export class Floatable {
2
- activate: number | undefined;
3
- active = false;
4
- deactivate: number | undefined;
5
- frame: DOMHighResTimeStamp | undefined;
6
-
7
- constructor(
8
- public anchor: HTMLElement,
9
- public content: HTMLElement,
10
- ) {}
11
-
12
- destroy(): void {
13
- this.activate = undefined;
14
- this.deactivate = undefined;
15
- this.frame = undefined;
16
-
17
- this.anchor = undefined as never;
18
- this.content = undefined as never;
2
+ activate: number | undefined;
3
+ active = false;
4
+ deactivate: number | undefined;
5
+ frame: DOMHighResTimeStamp | undefined;
6
+
7
+ constructor(
8
+ public anchor: HTMLElement,
9
+ public content: HTMLElement,
10
+ ) {
11
+ setStyles(content);
12
+ }
13
+
14
+ destroy(): void {
15
+ this.activate = undefined;
16
+ this.deactivate = undefined;
17
+ this.frame = undefined;
18
+
19
+ this.anchor = undefined as never;
20
+ this.content = undefined as never;
21
+ }
19
22
  }
20
- }
21
23
 
22
24
  export function activate(
23
25
  floatable: Floatable,
24
26
  active: Set<Floatable>,
27
+ order?: Floatable[],
25
28
  time?: number,
26
29
  ): void {
27
30
  stop(floatable);
@@ -31,10 +34,10 @@ export function activate(
31
34
  }
32
35
 
33
36
  if (time == null || time === 0) {
34
- onActivate(floatable, active);
37
+ onActivate(floatable, active, order);
35
38
  } else {
36
39
  floatable.activate = +setTimeout(() => {
37
- onActivate(floatable, active);
40
+ onActivate(floatable, active, order);
38
41
  }, time);
39
42
  }
40
43
  }
@@ -42,6 +45,7 @@ export function activate(
42
45
  export function deactivate(
43
46
  floatable: Floatable,
44
47
  active: Set<Floatable>,
48
+ order?: Floatable[],
45
49
  time?: number,
46
50
  ): void {
47
51
  stop(floatable);
@@ -51,10 +55,10 @@ export function deactivate(
51
55
  }
52
56
 
53
57
  if (time == null || time === 0) {
54
- onDeactivate(floatable, active);
58
+ onDeactivate(floatable, active, order);
55
59
  } else {
56
60
  floatable.deactivate = +setTimeout(() => {
57
- onDeactivate(floatable, active);
61
+ onDeactivate(floatable, active, order);
58
62
  }, time);
59
63
  }
60
64
  }
@@ -106,11 +110,19 @@ function getY(anchor: DOMRect, content: DOMRect): number {
106
110
  return anchor.top + (anchor.height - content.height) / 2;
107
111
  }
108
112
 
109
- function onActivate(floatable: Floatable, active: Set<Floatable>): void {
113
+ function onActivate(
114
+ floatable: Floatable,
115
+ active: Set<Floatable>,
116
+ order?: Floatable[],
117
+ ): void {
110
118
  floatable.active = true;
111
119
 
112
120
  active.add(floatable);
113
121
 
122
+ if (order != null) {
123
+ order.push(floatable);
124
+ }
125
+
114
126
  document.body.append(floatable.content);
115
127
 
116
128
  floatable.content.hidden = false;
@@ -118,11 +130,21 @@ function onActivate(floatable: Floatable, active: Set<Floatable>): void {
118
130
  update(floatable);
119
131
  }
120
132
 
121
- function onDeactivate(floatable: Floatable, active: Set<Floatable>): void {
133
+ function onDeactivate(
134
+ floatable: Floatable,
135
+ active: Set<Floatable>,
136
+ order?: Floatable[],
137
+ ): void {
122
138
  floatable.active = false;
123
139
 
124
140
  active.delete(floatable);
125
141
 
142
+ const index = order?.indexOf(floatable) ?? -1;
143
+
144
+ if (index > -1) {
145
+ order?.splice(index, 1);
146
+ }
147
+
126
148
  floatable.content.hidden = true;
127
149
 
128
150
  floatable.anchor.insertAdjacentElement('afterend', floatable.content);
@@ -144,6 +166,12 @@ function update(floatable: Floatable): void {
144
166
  floatable.frame = requestAnimationFrame(run);
145
167
  }
146
168
 
169
+ function setStyles(content: HTMLElement): void {
170
+ content.style.position = 'fixed';
171
+ content.style.inset = '0 auto auto 0';
172
+ content.style.zIndex = '10';
173
+ }
174
+
147
175
  function stop(floatable: Floatable): void {
148
176
  if (floatable.frame != null) {
149
177
  cancelAnimationFrame(floatable.frame);
@@ -165,4 +193,4 @@ function stop(floatable: Floatable): void {
165
193
  //
166
194
 
167
195
  const margin =
168
- Number.parseInt(getComputedStyle(document.documentElement).fontSize, 10) / 4;
196
+ Number.parseInt(getComputedStyle(document.documentElement).fontSize, 10) / 2;