actual-css 0.1.0 → 0.3.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.
- package/CHANGELOG.md +90 -1
- package/README.md +25 -8
- package/dist/actual-themes.min.css +1 -0
- package/dist/actual.css +1492 -931
- package/dist/actual.js +1 -3
- package/dist/actual.min.css +1 -1
- package/dist/optional.css +1048 -0
- package/dist/optional.min.css +1 -0
- package/docs/design-notes/browser-support.md +95 -6
- package/docs/design-notes/cascade-layer.md +1 -1
- package/docs/design-notes/enhancement-contract.md +101 -0
- package/docs/design-notes/forced-colors.md +105 -0
- package/docs/design-notes/naming.md +3 -1
- package/docs/design-notes/platform-alignment.md +492 -0
- package/docs/design-notes/references.md +8 -0
- package/docs/design-notes/widget-primitives.md +119 -0
- package/docs/navigation.json +135 -0
- package/docs/pages/components/accordion.md +80 -0
- package/docs/pages/components/alert.md +203 -0
- package/docs/pages/components/aura.md +35 -0
- package/docs/pages/components/avatar.md +95 -0
- package/docs/pages/components/badge.md +102 -0
- package/docs/pages/components/breadcrumb.md +19 -0
- package/docs/pages/components/button.md +205 -0
- package/docs/pages/components/card.md +183 -0
- package/docs/pages/components/chat.md +50 -0
- package/docs/pages/components/dialog.md +367 -0
- package/docs/pages/components/drawer.md +113 -0
- package/docs/pages/components/fab.md +118 -0
- package/docs/pages/components/key.md +33 -0
- package/docs/pages/components/meter.md +23 -0
- package/docs/pages/components/navbar.md +92 -0
- package/docs/pages/components/pagination.md +55 -0
- package/docs/pages/components/progress.md +29 -0
- package/docs/pages/components/skeleton.md +38 -0
- package/docs/pages/components/spinner.md +93 -0
- package/docs/pages/components/table.md +102 -0
- package/docs/pages/enhancements/flyout.md +391 -0
- package/docs/pages/enhancements/overview.md +36 -0
- package/docs/pages/enhancements/scrollspy.md +71 -0
- package/docs/pages/enhancements/status-bar.md +72 -0
- package/docs/pages/enhancements/tabs.md +80 -0
- package/docs/pages/enhancements/tooltip.md +130 -0
- package/docs/pages/examples/overview.md +41 -0
- package/docs/pages/forms/choice-cards.md +102 -0
- package/docs/pages/forms/controls.md +32 -0
- package/docs/pages/forms/detached-actions.md +59 -0
- package/docs/pages/forms/floating-field.md +88 -0
- package/docs/pages/forms/form-actions.md +56 -0
- package/docs/pages/forms/input-policy.md +91 -0
- package/docs/pages/forms/joined-controls.md +37 -0
- package/docs/pages/forms/labels.md +42 -0
- package/docs/pages/forms/otp.md +50 -0
- package/docs/pages/forms/overview.md +188 -0
- package/docs/pages/forms/password.md +40 -0
- package/docs/pages/forms/range.md +17 -0
- package/docs/pages/forms/states-and-sizes.md +321 -0
- package/docs/pages/forms/sticky-actions.md +187 -0
- package/docs/pages/forms/switch.md +31 -0
- package/docs/pages/forms/validation.md +241 -0
- package/docs/{mental-model.md → pages/foundations/philosophy.md} +33 -18
- package/docs/pages/foundations/theming.md +92 -0
- package/docs/pages/foundations/tokens.md +421 -0
- package/docs/{typography.md → pages/foundations/typography.md} +27 -9
- package/docs/pages/guides/bootstrap.md +438 -0
- package/docs/pages/guides/introduction.md +157 -0
- package/docs/pages/guides/optional-components.md +33 -0
- package/docs/pages/guides/progressive-enhancement.md +686 -0
- package/docs/pages/guides/tailwind.md +455 -0
- package/docs/pages/layout/app-shell.md +25 -0
- package/docs/pages/layout/center.md +29 -0
- package/docs/pages/layout/cluster.md +57 -0
- package/docs/pages/layout/frame.md +30 -0
- package/docs/pages/layout/grid.md +142 -0
- package/docs/pages/layout/list.md +41 -0
- package/docs/pages/layout/media.md +21 -0
- package/docs/pages/layout/overview.md +37 -0
- package/docs/pages/layout/scroll-snap.md +54 -0
- package/docs/pages/layout/sidebar-layout.md +27 -0
- package/docs/pages/layout/stack.md +18 -0
- package/docs/pages/layout/switcher.md +36 -0
- package/docs/pages/layout/topbar.md +23 -0
- package/docs/pages/patterns/actions.md +52 -0
- package/docs/pages/patterns/code-block.md +36 -0
- package/docs/pages/patterns/exclusive-accordion.md +16 -0
- package/docs/pages/patterns/hierarchical-nav.md +26 -0
- package/docs/pages/patterns/icon-input.md +25 -0
- package/docs/pages/patterns/intent-nesting.md +3 -0
- package/docs/pages/patterns/navigation-list.md +235 -0
- package/docs/pages/patterns/overview.md +58 -0
- package/docs/pages/patterns/structured-lists.md +47 -0
- package/docs/pages/utilities/accessibility.md +34 -0
- package/docs/pages/utilities/flex.md +105 -0
- package/docs/pages/utilities/links.md +18 -0
- package/docs/pages/utilities/list.md +31 -0
- package/docs/pages/utilities/overflow.md +43 -0
- package/docs/pages/utilities/overview.md +18 -0
- package/docs/pages/utilities/shape.md +25 -0
- package/docs/pages/utilities/sizing-wrapping.md +115 -0
- package/docs/pages/utilities/spacing.md +79 -0
- package/docs/pages/utilities/text.md +43 -0
- package/llms.txt +10 -4
- package/package.json +30 -16
- package/src/css/actual.css +12 -1
- package/src/css/base.css +3 -9
- package/src/css/components/accordion.css +18 -30
- package/src/css/components/actions.css +4 -1
- package/src/css/components/alert.css +162 -49
- package/src/css/components/avatar.css +16 -10
- package/src/css/components/badge.css +82 -39
- package/src/css/components/breadcrumb.css +7 -15
- package/src/css/components/busy.css +7 -0
- package/src/css/components/button.css +82 -38
- package/src/css/components/card.css +17 -37
- package/src/css/components/drawer.css +30 -13
- package/src/css/components/flyout.css +39 -110
- package/src/css/components/index.css +1 -0
- package/src/css/components/join.css +14 -3
- package/src/css/components/key.css +2 -0
- package/src/css/components/menu.css +125 -0
- package/src/css/components/meter.css +0 -23
- package/src/css/components/modal.css +162 -7
- package/src/css/components/navbar.css +17 -18
- package/src/css/components/overline.css +0 -7
- package/src/css/components/pagination.css +1 -1
- package/src/css/components/progress.css +8 -26
- package/src/css/components/scrollspy.css +2 -2
- package/src/css/components/skeleton.css +7 -0
- package/src/css/components/spinner.css +10 -11
- package/src/css/components/status-bar.css +18 -28
- package/src/css/components/surface.css +2 -8
- package/src/css/components/tab.css +20 -48
- package/src/css/components/table.css +7 -5
- package/src/css/components/tooltip.css +14 -7
- package/src/css/focus.css +16 -3
- package/src/css/forms/choice-card.css +13 -15
- package/src/css/forms/choice.css +21 -28
- package/src/css/forms/control.css +16 -26
- package/src/css/forms/custom-select.css +9 -11
- package/src/css/forms/form-actions.css +14 -3
- package/src/css/forms/form.css +7 -4
- package/src/css/forms/index.css +5 -4
- package/src/css/forms/input-icon.css +30 -12
- package/src/css/forms/native.css +48 -33
- package/src/css/forms/range.css +103 -0
- package/src/css/forms/select.css +8 -8
- package/src/css/forms/switch.css +14 -13
- package/src/css/forms/validation.css +32 -5
- package/src/css/grid.css +19 -18
- package/src/css/intents.css +2 -2
- package/src/css/layout.css +29 -1
- package/src/css/optional/aura.css +93 -0
- package/src/css/optional/chat.css +105 -0
- package/src/css/optional/fab.css +104 -0
- package/src/css/optional/floating-field.css +87 -0
- package/src/css/optional/index.css +5 -0
- package/src/css/optional/layout-extra.css +7 -4
- package/src/css/optional/otp.css +115 -0
- package/src/css/optional/scroll-snap.css +6 -0
- package/src/css/optional/scroller.css +19 -7
- package/src/css/optional/typography-fluid.css +5 -0
- package/src/css/optional/utilities-extra.css +17 -2
- package/src/css/prose.css +18 -8
- package/src/css/reset.css +0 -10
- package/src/css/theme.css +29 -13
- package/src/css/themes/cyberpunk.css +1 -1
- package/src/css/themes/dim.css +3 -3
- package/src/css/themes/edge.css +11 -37
- package/src/css/themes/index.css +3 -3
- package/src/css/themes/{dark.css → indigo.css} +5 -4
- package/src/css/themes/neon.css +5 -5
- package/src/css/themes/square.css +0 -3
- package/src/css/tokens.css +81 -21
- package/src/css/utilities.css +8 -10
- package/src/css/variants.css +37 -65
- package/src/js/command.js +1 -3
- package/src/js/context-menu.js +72 -53
- package/src/js/dialog.js +68 -79
- package/src/js/dismiss.js +15 -0
- package/src/js/enhance.js +76 -19
- package/src/js/enhancement-loader.js +301 -0
- package/src/js/events.js +3 -0
- package/src/js/filter.js +3 -3
- package/src/js/floating.js +101 -131
- package/src/js/flyout.js +106 -96
- package/src/js/focus-group.js +116 -0
- package/src/js/focus.js +6 -1
- package/src/js/index.js +4 -2
- package/src/js/input.js +1 -1
- package/src/js/keys.js +27 -0
- package/src/js/mask.js +7 -6
- package/src/js/menu.js +89 -35
- package/src/js/parse-config.js +47 -0
- package/src/js/password.js +9 -1
- package/src/js/scrollspy.js +133 -62
- package/src/js/selectors.js +14 -31
- package/src/js/status.js +16 -14
- package/src/js/surface.js +219 -125
- package/src/js/tab.js +46 -66
- package/src/js/tooltip.js +53 -31
- package/src/js/validation.js +45 -51
- package/dist/actual.js.map +0 -31
- package/docs/README.md +0 -26
- package/docs/components.md +0 -860
- package/docs/design-notes/previous-work.md +0 -5
- package/docs/forms.md +0 -1265
- package/docs/javascript.md +0 -426
- package/docs/layout.md +0 -480
- package/docs/patterns.md +0 -415
- package/docs/reserved.md +0 -7
- package/docs/tokens.md +0 -455
- package/docs/ui.md +0 -937
- package/docs/utilities.md +0 -384
- package/src/css/dialog-fallback.css +0 -56
- package/src/js/dialog-fallback.js +0 -163
package/src/js/floating.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Compact positioning engine for menus, tooltips, and popovers.
|
|
3
3
|
*
|
|
4
|
-
* Core math ported from Floating UI.
|
|
5
|
-
*
|
|
4
|
+
* Core math ported from Floating UI. autoUpdate() batches scroll, resize,
|
|
5
|
+
* and element-resize callbacks per ownerDocument so each document owns its
|
|
6
|
+
* own listener set — no global import-time side effects.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
|
-
import { EVENTS } from "./events.js";
|
|
9
|
-
|
|
10
9
|
/* ── Placement parsing ────────────────────────────────── */
|
|
11
10
|
|
|
12
11
|
function getSide(p) {
|
|
@@ -65,7 +64,7 @@ function computeCoords(reference, floating, placement, rtl) {
|
|
|
65
64
|
return coords;
|
|
66
65
|
}
|
|
67
66
|
|
|
68
|
-
function applyOffset(coords, side, offset
|
|
67
|
+
function applyOffset(coords, side, offset) {
|
|
69
68
|
switch (side) {
|
|
70
69
|
case "top":
|
|
71
70
|
coords.y -= offset;
|
|
@@ -74,10 +73,10 @@ function applyOffset(coords, side, offset, rtl) {
|
|
|
74
73
|
coords.y += offset;
|
|
75
74
|
break;
|
|
76
75
|
case "left":
|
|
77
|
-
coords.x
|
|
76
|
+
coords.x -= offset;
|
|
78
77
|
break;
|
|
79
78
|
case "right":
|
|
80
|
-
coords.x +=
|
|
79
|
+
coords.x += offset;
|
|
81
80
|
break;
|
|
82
81
|
}
|
|
83
82
|
}
|
|
@@ -103,7 +102,8 @@ function isRTL(el) {
|
|
|
103
102
|
if (el.dir === "rtl") return true;
|
|
104
103
|
if (el.dir === "ltr") return false;
|
|
105
104
|
if (supportsDirSelector) return el.matches(":dir(rtl)");
|
|
106
|
-
|
|
105
|
+
const style = el.ownerDocument?.defaultView?.getComputedStyle?.(el);
|
|
106
|
+
return style?.direction === "rtl";
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
function getDocEl(doc) {
|
|
@@ -134,8 +134,6 @@ function getViewportBoundary(doc) {
|
|
|
134
134
|
doc.compatMode === "CSS1Compat"
|
|
135
135
|
? toNumber(bodyStyle?.marginLeft) + toNumber(bodyStyle?.marginRight)
|
|
136
136
|
: 0;
|
|
137
|
-
// Classic stable scrollbars reserve inline space on the body. Subtract only
|
|
138
|
-
// plausible scrollbar widths so unusual body sizing does not shrink the viewport.
|
|
139
137
|
const stableScrollbar = Math.abs(docEl.clientWidth - body.clientWidth - bodyMargin);
|
|
140
138
|
if (stableScrollbar <= STABLE_SCROLLBAR_MAX_WIDTH) {
|
|
141
139
|
width -= stableScrollbar;
|
|
@@ -181,15 +179,6 @@ function isVisible(el) {
|
|
|
181
179
|
return el.getClientRects().length > 0;
|
|
182
180
|
}
|
|
183
181
|
|
|
184
|
-
function topVisibleTracked() {
|
|
185
|
-
const elements = [...tracked];
|
|
186
|
-
for (let i = elements.length - 1; i >= 0; i--) {
|
|
187
|
-
const el = elements[i];
|
|
188
|
-
if (el.isConnected && isVisible(el)) return el;
|
|
189
|
-
}
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
182
|
function getFloatingSize(float) {
|
|
194
183
|
const rect = float.getBoundingClientRect();
|
|
195
184
|
return {
|
|
@@ -198,138 +187,120 @@ function getFloatingSize(float) {
|
|
|
198
187
|
};
|
|
199
188
|
}
|
|
200
189
|
|
|
201
|
-
/* ──
|
|
202
|
-
|
|
203
|
-
const tracked = new Set();
|
|
204
|
-
let resizeObserver = null;
|
|
205
|
-
let tick = false;
|
|
206
|
-
const pendingTypes = new Set();
|
|
207
|
-
|
|
208
|
-
function notify(type) {
|
|
209
|
-
if (type === "escape") {
|
|
210
|
-
const el = topVisibleTracked();
|
|
211
|
-
if (!el) return;
|
|
212
|
-
el.dispatchEvent(
|
|
213
|
-
new CustomEvent(EVENTS.hide, {
|
|
214
|
-
bubbles: false,
|
|
215
|
-
detail: { type: "escape" },
|
|
216
|
-
}),
|
|
217
|
-
);
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
190
|
+
/* ── Per-document batched callbacks ────────────────────── */
|
|
220
191
|
|
|
221
|
-
|
|
222
|
-
el.dispatchEvent(
|
|
223
|
-
new CustomEvent(EVENTS.reposition, {
|
|
224
|
-
bubbles: false,
|
|
225
|
-
detail: { type },
|
|
226
|
-
}),
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
192
|
+
const trackers = new WeakMap();
|
|
230
193
|
|
|
231
|
-
function
|
|
232
|
-
|
|
233
|
-
|
|
194
|
+
function createTracker(doc) {
|
|
195
|
+
const win = doc.defaultView;
|
|
196
|
+
const ResizeObserver = win?.ResizeObserver;
|
|
234
197
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const types = [...pendingTypes];
|
|
240
|
-
pendingTypes.clear();
|
|
241
|
-
for (const type of types) notify(type);
|
|
242
|
-
tick = false;
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
tick = true;
|
|
246
|
-
}
|
|
198
|
+
const entries = new Map();
|
|
199
|
+
let resizeObserver = null;
|
|
200
|
+
let tick = false;
|
|
201
|
+
const pending = new Map();
|
|
247
202
|
|
|
248
|
-
function getResizeObserver() {
|
|
249
|
-
|
|
203
|
+
function getResizeObserver() {
|
|
204
|
+
if (resizeObserver || !ResizeObserver) return resizeObserver;
|
|
205
|
+
|
|
206
|
+
resizeObserver = new ResizeObserver((entries, observer) => {
|
|
207
|
+
for (const entry of entries) {
|
|
208
|
+
observer.unobserve(entry.target);
|
|
209
|
+
rafNotify("element-resize", entry);
|
|
210
|
+
win.requestAnimationFrame(() => {
|
|
211
|
+
if (tracker.entries.has(entry.target)) observer.observe(entry.target);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
});
|
|
250
215
|
return resizeObserver;
|
|
251
216
|
}
|
|
252
217
|
|
|
253
|
-
|
|
254
|
-
for (const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
218
|
+
function rafNotify(type, source) {
|
|
219
|
+
for (const element of entries.keys()) {
|
|
220
|
+
let notifications = pending.get(element);
|
|
221
|
+
if (!notifications) {
|
|
222
|
+
notifications = new Map();
|
|
223
|
+
pending.set(element, notifications);
|
|
224
|
+
}
|
|
225
|
+
let notification = notifications.get(type);
|
|
226
|
+
if (!notification) {
|
|
227
|
+
notification = { targets: new Set(), timeStamp: 0 };
|
|
228
|
+
notifications.set(type, notification);
|
|
229
|
+
}
|
|
230
|
+
if (source?.target) notification.targets.add(source.target);
|
|
231
|
+
notification.timeStamp = Math.max(notification.timeStamp, source?.timeStamp || 0);
|
|
232
|
+
}
|
|
233
|
+
if (!tick) {
|
|
234
|
+
win.requestAnimationFrame(() => {
|
|
235
|
+
const notifications = [...pending];
|
|
236
|
+
pending.clear();
|
|
237
|
+
for (const [element, types] of notifications) {
|
|
238
|
+
const cb = entries.get(element);
|
|
239
|
+
if (!cb || !element.isConnected) continue;
|
|
240
|
+
for (const [pendingType, detail] of types) {
|
|
241
|
+
cb({ type: pendingType, ...detail });
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
tick = false;
|
|
259
245
|
});
|
|
260
246
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
247
|
+
tick = true;
|
|
248
|
+
}
|
|
264
249
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
document.addEventListener("keydown", (e) => {
|
|
269
|
-
if (e.key === "Escape" && !e.ctrlKey && !e.altKey && !e.shiftKey) {
|
|
270
|
-
if (!hasOpenSurface()) return;
|
|
271
|
-
e.preventDefault();
|
|
272
|
-
notify("escape");
|
|
273
|
-
}
|
|
250
|
+
doc.addEventListener("scroll", (e) => rafNotify(e.type, e), {
|
|
251
|
+
passive: true,
|
|
252
|
+
capture: true,
|
|
274
253
|
});
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
254
|
+
win.addEventListener("resize", (e) => rafNotify("resize", e), { passive: true });
|
|
255
|
+
|
|
256
|
+
const tracker = {
|
|
257
|
+
entries,
|
|
258
|
+
add(element, callback) {
|
|
259
|
+
entries.set(element, callback);
|
|
260
|
+
getResizeObserver()?.observe(element);
|
|
261
|
+
return () => {
|
|
262
|
+
entries.delete(element);
|
|
263
|
+
pending.delete(element);
|
|
264
|
+
resizeObserver?.unobserve(element);
|
|
265
|
+
};
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
return tracker;
|
|
279
270
|
}
|
|
280
271
|
|
|
281
|
-
|
|
272
|
+
function trackerFor(element) {
|
|
273
|
+
const doc = element.ownerDocument;
|
|
274
|
+
let tracker = trackers.get(doc);
|
|
275
|
+
if (!tracker) {
|
|
276
|
+
tracker = createTracker(doc);
|
|
277
|
+
trackers.set(doc, tracker);
|
|
278
|
+
}
|
|
279
|
+
return tracker;
|
|
280
|
+
}
|
|
282
281
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
* Returns a cleanup function that removes the element from tracking.
|
|
286
|
-
*/
|
|
287
|
-
export function track(el) {
|
|
288
|
-
tracked.add(el);
|
|
289
|
-
getResizeObserver()?.observe(el);
|
|
290
|
-
return () => {
|
|
291
|
-
tracked.delete(el);
|
|
292
|
-
resizeObserver?.unobserve(el);
|
|
293
|
-
};
|
|
282
|
+
export function autoUpdate(element, callback) {
|
|
283
|
+
return trackerFor(element).add(element, callback);
|
|
294
284
|
}
|
|
295
285
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
*
|
|
299
|
-
* @param {Element} ref
|
|
300
|
-
* @param {HTMLElement} float
|
|
301
|
-
* @param {object} [opts]
|
|
302
|
-
* @param {string} [opts.placement] default "bottom-start"
|
|
303
|
-
* @param {number} [opts.distance] px offset from reference
|
|
304
|
-
* @param {boolean} [opts.flip] flip if overflows viewport
|
|
305
|
-
* @param {boolean} [opts.shift] shift if overflows x-axis
|
|
306
|
-
* @param {number} [opts.shiftPadding] px padding when shifting
|
|
307
|
-
* @param {Element} [opts.scope] constrain to a bounding element
|
|
308
|
-
*/
|
|
286
|
+
/* ── Positioning ──────────────────────────────────────── */
|
|
287
|
+
|
|
309
288
|
export function reposition(ref, float, opts = {}) {
|
|
310
|
-
if (!isVisible(float)) return;
|
|
289
|
+
if (!isVisible(float)) return false;
|
|
311
290
|
|
|
312
291
|
const placement = opts.placement || "bottom-start";
|
|
313
292
|
const distance = opts.distance || 0;
|
|
314
293
|
const flip = opts.flip !== false;
|
|
315
294
|
const shift = opts.shift !== false;
|
|
316
|
-
const shiftPad = opts.shiftPadding
|
|
295
|
+
const shiftPad = opts.shiftPadding ?? 4;
|
|
317
296
|
const rtl = isRTL(ref);
|
|
318
297
|
|
|
319
298
|
const rects = ref.getClientRects();
|
|
320
299
|
const refRect = placement.startsWith("bottom") ? rects[rects.length - 1] : rects[0];
|
|
321
|
-
if (!refRect) return;
|
|
300
|
+
if (!refRect) return false;
|
|
322
301
|
|
|
323
302
|
const boundary = getBoundary(ref, opts);
|
|
324
|
-
if (isOutsideBoundary(refRect, boundary))
|
|
325
|
-
float.dispatchEvent(
|
|
326
|
-
new CustomEvent(EVENTS.outOfView, {
|
|
327
|
-
bubbles: false,
|
|
328
|
-
detail: { type: "out-of-view" },
|
|
329
|
-
}),
|
|
330
|
-
);
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
303
|
+
if (isOutsideBoundary(refRect, boundary)) return false;
|
|
333
304
|
|
|
334
305
|
const floatRect = getFloatingSize(float);
|
|
335
306
|
const sx = boundary.x;
|
|
@@ -342,7 +313,7 @@ export function reposition(ref, float, opts = {}) {
|
|
|
342
313
|
let axis = getMainAxis(placement);
|
|
343
314
|
let current = placement;
|
|
344
315
|
let coords = computeCoords(refRect, floatRect, current, rtl);
|
|
345
|
-
applyOffset(coords, side, distance
|
|
316
|
+
applyOffset(coords, side, distance);
|
|
346
317
|
|
|
347
318
|
if (flip) {
|
|
348
319
|
const cx = Math.ceil(coords.x);
|
|
@@ -355,11 +326,9 @@ export function reposition(ref, float, opts = {}) {
|
|
|
355
326
|
side = flipSide(side);
|
|
356
327
|
current = align ? `${side}-${align}` : side;
|
|
357
328
|
coords = computeCoords(refRect, floatRect, current, rtl);
|
|
358
|
-
applyOffset(coords, side, distance
|
|
329
|
+
applyOffset(coords, side, distance);
|
|
359
330
|
}
|
|
360
331
|
|
|
361
|
-
// Narrow side placements can fit neither left nor right. In that case,
|
|
362
|
-
// fall back to top so compact mobile layouts get a usable placement.
|
|
363
332
|
if (
|
|
364
333
|
axis === "y" &&
|
|
365
334
|
(coords.x < sx || coords.x + floatRect.width > cw) &&
|
|
@@ -369,7 +338,7 @@ export function reposition(ref, float, opts = {}) {
|
|
|
369
338
|
axis = "x";
|
|
370
339
|
current = align ? `${side}-${align}` : side;
|
|
371
340
|
coords = computeCoords(refRect, floatRect, current, rtl);
|
|
372
|
-
applyOffset(coords, side, distance
|
|
341
|
+
applyOffset(coords, side, distance);
|
|
373
342
|
}
|
|
374
343
|
}
|
|
375
344
|
|
|
@@ -382,7 +351,7 @@ export function reposition(ref, float, opts = {}) {
|
|
|
382
351
|
const nextAlign = getAlignment(current) === "end" ? "start" : "end";
|
|
383
352
|
const candidatePlacement = `${side}-${nextAlign}`;
|
|
384
353
|
const candidate = computeCoords(refRect, floatRect, candidatePlacement, rtl);
|
|
385
|
-
applyOffset(candidate, side, distance
|
|
354
|
+
applyOffset(candidate, side, distance);
|
|
386
355
|
|
|
387
356
|
if (getInlineOverflow(candidate, floatRect, minX, maxX) < currentOverflow) {
|
|
388
357
|
current = candidatePlacement;
|
|
@@ -391,7 +360,6 @@ export function reposition(ref, float, opts = {}) {
|
|
|
391
360
|
}
|
|
392
361
|
}
|
|
393
362
|
|
|
394
|
-
// shift on x axis
|
|
395
363
|
let p = 50;
|
|
396
364
|
if (shift || floatRect.width > refRect.width) {
|
|
397
365
|
const minX = sx + shiftPad;
|
|
@@ -433,6 +401,8 @@ export function reposition(ref, float, opts = {}) {
|
|
|
433
401
|
left: `${coords.x}px`,
|
|
434
402
|
top: `${coords.y}px`,
|
|
435
403
|
});
|
|
404
|
+
|
|
405
|
+
return true;
|
|
436
406
|
}
|
|
437
407
|
|
|
438
408
|
export function repositionAt(x, y, float, opts = {}) {
|
|
@@ -458,5 +428,5 @@ export function repositionAt(x, y, float, opts = {}) {
|
|
|
458
428
|
],
|
|
459
429
|
};
|
|
460
430
|
|
|
461
|
-
reposition(ref, float, opts);
|
|
431
|
+
return reposition(ref, float, opts);
|
|
462
432
|
}
|
package/src/js/flyout.js
CHANGED
|
@@ -1,58 +1,54 @@
|
|
|
1
|
-
|
|
2
|
-
* Flyout trigger — trigger adapter over the shared action-surface runtime.
|
|
3
|
-
*
|
|
4
|
-
* App menus keep roving focus and menuitem activation. Nav panels reuse the
|
|
5
|
-
* same surface lifecycle without menu semantics.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import enhance from "./enhance.js";
|
|
1
|
+
import { registerEnhancement } from "./enhance.js";
|
|
9
2
|
import { focusFirstDescendant } from "./focus.js";
|
|
10
|
-
import { focusFirstMenuItem, focusLastMenuItem
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
disconnectSurface,
|
|
15
|
-
isSurfaceOpen,
|
|
16
|
-
openSurface,
|
|
17
|
-
prepareSurface,
|
|
18
|
-
} from "./surface.js";
|
|
19
|
-
|
|
20
|
-
// trigger -> { flyout, controller }
|
|
3
|
+
import { connectMenu, focusFirstMenuItem, focusLastMenuItem } from "./menu.js";
|
|
4
|
+
import { closeSurface, isSurfaceOpen, openSurface, retainSurface } from "./surface.js";
|
|
5
|
+
|
|
6
|
+
const panelRefs = new WeakMap();
|
|
21
7
|
const triggerMap = new WeakMap();
|
|
22
|
-
const FLYOUT_SELECTOR = `.${CLASSES.flyout}`;
|
|
23
|
-
const FLYOUT_TRIGGER_SELECTOR = "[aria-controls][aria-expanded]";
|
|
24
8
|
|
|
25
|
-
function
|
|
26
|
-
|
|
9
|
+
function normalizeBreakpoint(raw) {
|
|
10
|
+
const BREAKPOINTS = { sm: 640, md: 768, lg: 1024 };
|
|
11
|
+
const value = Number.parseInt(BREAKPOINTS[raw] || raw, 10);
|
|
12
|
+
return Number.isFinite(value) ? value : undefined;
|
|
27
13
|
}
|
|
28
14
|
|
|
29
|
-
function
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
15
|
+
function readFlyoutOptions(panel) {
|
|
16
|
+
const opts = { autoClose: "true" };
|
|
17
|
+
const ds = panel.dataset;
|
|
18
|
+
if (ds.flyoutPlacement) opts.placement = ds.flyoutPlacement;
|
|
19
|
+
if (ds.flyoutDistance != null) {
|
|
20
|
+
const d = Number.parseFloat(ds.flyoutDistance);
|
|
21
|
+
if (Number.isFinite(d)) opts.distance = d;
|
|
22
|
+
}
|
|
23
|
+
if (ds.flyoutMobile) opts.mobile = ds.flyoutMobile;
|
|
24
|
+
if (ds.flyoutBreakpoint) {
|
|
25
|
+
const bp = normalizeBreakpoint(ds.flyoutBreakpoint);
|
|
26
|
+
if (bp != null) opts.breakpoint = bp;
|
|
27
|
+
}
|
|
28
|
+
if (ds.flyoutAutoClose) opts.autoClose = ds.flyoutAutoClose;
|
|
29
|
+
return opts;
|
|
33
30
|
}
|
|
34
31
|
|
|
35
|
-
function
|
|
36
|
-
|
|
32
|
+
function panelFor(trigger) {
|
|
33
|
+
const id = trigger.getAttribute("aria-controls");
|
|
34
|
+
return id ? trigger.ownerDocument.getElementById(id) : null;
|
|
37
35
|
}
|
|
38
36
|
|
|
39
|
-
function
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const flyout = flyoutFor(trigger);
|
|
43
|
-
if (!flyout) return null;
|
|
37
|
+
function isMenuFlyout(panel) {
|
|
38
|
+
return panel.matches("menu") || panel.classList.contains("menu");
|
|
39
|
+
}
|
|
44
40
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
trigger
|
|
49
|
-
|
|
50
|
-
|
|
41
|
+
function openFlyout(panel, trigger) {
|
|
42
|
+
return openSurface(panel, {
|
|
43
|
+
trigger,
|
|
44
|
+
source: trigger,
|
|
45
|
+
...readFlyoutOptions(panel),
|
|
46
|
+
});
|
|
51
47
|
}
|
|
52
48
|
|
|
53
49
|
function openAndFocusPanel(flyout, trigger) {
|
|
54
|
-
if (!isSurfaceOpen(flyout)
|
|
55
|
-
focusFirstDescendant(flyout);
|
|
50
|
+
if (!isSurfaceOpen(flyout) && !openFlyout(flyout, trigger)) return false;
|
|
51
|
+
return focusFirstDescendant(flyout);
|
|
56
52
|
}
|
|
57
53
|
|
|
58
54
|
function onTriggerClick(e) {
|
|
@@ -60,35 +56,34 @@ function onTriggerClick(e) {
|
|
|
60
56
|
const state = triggerMap.get(trigger);
|
|
61
57
|
if (!state) return;
|
|
62
58
|
e.stopPropagation();
|
|
63
|
-
const
|
|
64
|
-
if (!
|
|
65
|
-
if (isSurfaceOpen(
|
|
66
|
-
else openFlyout(
|
|
59
|
+
const panel = resolvePanel(trigger, state);
|
|
60
|
+
if (!panel) return;
|
|
61
|
+
if (isSurfaceOpen(panel)) closeSurface(panel);
|
|
62
|
+
else openFlyout(panel, trigger);
|
|
67
63
|
}
|
|
68
64
|
|
|
69
65
|
function onTriggerKeydown(e) {
|
|
70
66
|
const trigger = e.currentTarget;
|
|
71
67
|
const state = triggerMap.get(trigger);
|
|
72
68
|
if (!state) return;
|
|
73
|
-
const
|
|
74
|
-
if (!
|
|
75
|
-
const
|
|
76
|
-
const isActionList = items.length > 0;
|
|
69
|
+
const panel = resolvePanel(trigger, state);
|
|
70
|
+
if (!panel) return;
|
|
71
|
+
const isActionList = isMenuFlyout(panel);
|
|
77
72
|
|
|
78
73
|
if (!isActionList) {
|
|
79
74
|
switch (e.key) {
|
|
80
75
|
case "ArrowDown":
|
|
81
76
|
e.preventDefault();
|
|
82
|
-
openAndFocusPanel(
|
|
77
|
+
openAndFocusPanel(panel, trigger);
|
|
83
78
|
break;
|
|
84
79
|
case "Enter":
|
|
85
80
|
case " ":
|
|
86
81
|
e.preventDefault();
|
|
87
|
-
if (isSurfaceOpen(
|
|
88
|
-
else openAndFocusPanel(
|
|
82
|
+
if (isSurfaceOpen(panel)) closeSurface(panel);
|
|
83
|
+
else openAndFocusPanel(panel, trigger);
|
|
89
84
|
break;
|
|
90
85
|
case "Tab":
|
|
91
|
-
if (isSurfaceOpen(
|
|
86
|
+
if (isSurfaceOpen(panel) && !e.shiftKey && focusFirstDescendant(panel)) {
|
|
92
87
|
e.preventDefault();
|
|
93
88
|
}
|
|
94
89
|
break;
|
|
@@ -99,89 +94,104 @@ function onTriggerKeydown(e) {
|
|
|
99
94
|
switch (e.key) {
|
|
100
95
|
case "ArrowDown":
|
|
101
96
|
e.preventDefault();
|
|
102
|
-
if (!isSurfaceOpen(
|
|
103
|
-
focusFirstMenuItem(
|
|
97
|
+
if (!isSurfaceOpen(panel) && !openFlyout(panel, trigger)) break;
|
|
98
|
+
focusFirstMenuItem(panel);
|
|
104
99
|
break;
|
|
105
100
|
case "ArrowUp":
|
|
106
101
|
e.preventDefault();
|
|
107
|
-
if (!isSurfaceOpen(
|
|
108
|
-
focusLastMenuItem(
|
|
102
|
+
if (!isSurfaceOpen(panel) && !openFlyout(panel, trigger)) break;
|
|
103
|
+
focusLastMenuItem(panel);
|
|
109
104
|
break;
|
|
110
105
|
case "Home":
|
|
111
106
|
e.preventDefault();
|
|
112
|
-
if (!isSurfaceOpen(
|
|
113
|
-
focusFirstMenuItem(
|
|
107
|
+
if (!isSurfaceOpen(panel) && !openFlyout(panel, trigger)) break;
|
|
108
|
+
focusFirstMenuItem(panel);
|
|
114
109
|
break;
|
|
115
110
|
case "End":
|
|
116
111
|
e.preventDefault();
|
|
117
|
-
if (!isSurfaceOpen(
|
|
118
|
-
focusLastMenuItem(
|
|
112
|
+
if (!isSurfaceOpen(panel) && !openFlyout(panel, trigger)) break;
|
|
113
|
+
focusLastMenuItem(panel);
|
|
119
114
|
break;
|
|
120
115
|
case "Enter":
|
|
121
116
|
case " ":
|
|
122
117
|
e.preventDefault();
|
|
123
|
-
if (!isSurfaceOpen(
|
|
124
|
-
openFlyout(
|
|
125
|
-
focusFirstMenuItem(flyout);
|
|
118
|
+
if (!isSurfaceOpen(panel)) {
|
|
119
|
+
if (openFlyout(panel, trigger)) focusFirstMenuItem(panel);
|
|
126
120
|
} else {
|
|
127
|
-
closeSurface(
|
|
121
|
+
closeSurface(panel);
|
|
128
122
|
}
|
|
129
123
|
break;
|
|
130
124
|
case "Tab":
|
|
131
|
-
if (isSurfaceOpen(
|
|
125
|
+
if (isSurfaceOpen(panel)) closeSurface(panel);
|
|
132
126
|
break;
|
|
133
127
|
}
|
|
134
128
|
}
|
|
135
129
|
|
|
136
130
|
function connectTrigger(trigger) {
|
|
137
131
|
if (triggerMap.has(trigger)) return;
|
|
138
|
-
const flyout = flyoutFor(trigger);
|
|
139
|
-
if (!flyout) return;
|
|
140
132
|
|
|
141
133
|
const controller = new AbortController();
|
|
142
|
-
|
|
134
|
+
const state = { panel: null, controller, releaseMenu: null };
|
|
135
|
+
triggerMap.set(trigger, state);
|
|
136
|
+
|
|
137
|
+
resolvePanel(trigger, state);
|
|
143
138
|
|
|
144
|
-
prepareSurface(flyout);
|
|
145
139
|
if (!trigger.hasAttribute("aria-expanded")) {
|
|
146
140
|
trigger.setAttribute("aria-expanded", "false");
|
|
147
141
|
}
|
|
148
|
-
if (!trigger.hasAttribute("aria-haspopup") && isMenuFlyout(flyout)) {
|
|
149
|
-
trigger.setAttribute("aria-haspopup", "menu");
|
|
150
|
-
}
|
|
151
142
|
|
|
152
143
|
trigger.addEventListener("click", onTriggerClick, { signal: controller.signal });
|
|
153
144
|
trigger.addEventListener("keydown", onTriggerKeydown, { signal: controller.signal });
|
|
154
145
|
}
|
|
155
146
|
|
|
147
|
+
function resolvePanel(trigger, state) {
|
|
148
|
+
if (state.panel?.isConnected) return state.panel;
|
|
149
|
+
|
|
150
|
+
const panel = panelFor(trigger);
|
|
151
|
+
if (!panel) return null;
|
|
152
|
+
|
|
153
|
+
let panelEntry = panelRefs.get(panel);
|
|
154
|
+
if (!panelEntry) {
|
|
155
|
+
const release = retainSurface(panel);
|
|
156
|
+
panelEntry = { triggers: new Set(), release };
|
|
157
|
+
panelRefs.set(panel, panelEntry);
|
|
158
|
+
}
|
|
159
|
+
panelEntry.triggers.add(trigger);
|
|
160
|
+
state.panel = panel;
|
|
161
|
+
|
|
162
|
+
if (isMenuFlyout(panel)) {
|
|
163
|
+
state.releaseMenu = connectMenu(panel, {
|
|
164
|
+
close: (menu) => closeSurface(menu),
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (!trigger.hasAttribute("aria-haspopup") && isMenuFlyout(panel)) {
|
|
168
|
+
trigger.setAttribute("aria-haspopup", "menu");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return panel;
|
|
172
|
+
}
|
|
173
|
+
|
|
156
174
|
function disconnectTrigger(trigger) {
|
|
157
175
|
const state = triggerMap.get(trigger);
|
|
158
176
|
if (!state) return;
|
|
177
|
+
state.releaseMenu?.();
|
|
159
178
|
state.controller.abort();
|
|
179
|
+
if (state.panel?.isConnected && isSurfaceOpen(state.panel)) {
|
|
180
|
+
closeSurface(state.panel, { restoreFocus: false });
|
|
181
|
+
}
|
|
160
182
|
triggerMap.delete(trigger);
|
|
161
|
-
}
|
|
162
183
|
|
|
163
|
-
|
|
164
|
-
if (
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
184
|
+
const panelEntry = state.panel ? panelRefs.get(state.panel) : undefined;
|
|
185
|
+
if (panelEntry) {
|
|
186
|
+
panelEntry.triggers.delete(trigger);
|
|
187
|
+
if (panelEntry.triggers.size === 0) {
|
|
188
|
+
panelEntry.release();
|
|
189
|
+
panelRefs.delete(state.panel);
|
|
190
|
+
}
|
|
170
191
|
}
|
|
171
192
|
}
|
|
172
193
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
enhance({
|
|
179
|
-
[FLYOUT_TRIGGER_SELECTOR]: (trigger) => {
|
|
180
|
-
connectTrigger(trigger);
|
|
181
|
-
return () => disconnectTrigger(trigger);
|
|
182
|
-
},
|
|
183
|
-
[FLYOUT_SELECTOR]: (flyout) => {
|
|
184
|
-
connectFlyout(flyout);
|
|
185
|
-
return () => disconnectFlyout(flyout);
|
|
186
|
-
},
|
|
194
|
+
registerEnhancement("flyout", (trigger) => {
|
|
195
|
+
connectTrigger(trigger);
|
|
196
|
+
return () => disconnectTrigger(trigger);
|
|
187
197
|
});
|