@marianmeres/stuic 3.151.0 → 3.152.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,131 @@
1
+ /**
2
+ * Helpers for working with the containing block (CB) of `position: fixed`
3
+ * elements.
4
+ *
5
+ * A fixed-positioned element resolves against the viewport UNLESS an ancestor
6
+ * establishes a fixed containing block — via `transform`/`translate`/`rotate`/
7
+ * `scale`/`perspective`/`filter`/`backdrop-filter`, a `will-change` naming any
8
+ * of those, or layout/paint containment (`contain: layout|paint|strict|content`,
9
+ * which `content-visibility: auto` also applies). Overlay code that measures
10
+ * "does this fixed element fit" must therefore compare against the CB rect,
11
+ * not `window.innerWidth/innerHeight` — the two only coincide in the (common)
12
+ * no-such-ancestor case.
13
+ */
14
+ /**
15
+ * `will-change` values that force a fixed containing block: any property whose
16
+ * non-initial value would form one. (Same set floating-ui checks.)
17
+ */
18
+ const WILL_CHANGE_RE = /transform|translate|scale|rotate|perspective|filter/;
19
+ /** `contain` values that apply layout and/or paint containment. */
20
+ const CONTAIN_RE = /paint|layout|strict|content/;
21
+ let _isWebKit;
22
+ /**
23
+ * WebKit (Safari) detection — the same probe floating-ui uses. Only Safari
24
+ * supports the `-webkit-` prefixed backdrop-filter.
25
+ */
26
+ function isWebKit() {
27
+ if (_isWebKit === undefined) {
28
+ _isWebKit =
29
+ typeof CSS !== "undefined" &&
30
+ typeof CSS.supports === "function" &&
31
+ CSS.supports("-webkit-backdrop-filter", "none");
32
+ }
33
+ return _isWebKit;
34
+ }
35
+ function notNone(value) {
36
+ return !!value && value !== "none";
37
+ }
38
+ // Elements promoted to the TOP LAYER (modal dialogs via `showModal()`, open
39
+ // `[popover]`s, fullscreen elements) escape every ancestor containing block by
40
+ // design — their fixed descendants resolve against the viewport again. Each
41
+ // selector is probed separately: an engine that doesn't know one would
42
+ // otherwise reject the whole list.
43
+ const TOP_LAYER_SELECTORS = [":modal", ":popover-open", ":fullscreen"];
44
+ function isTopLayer(el) {
45
+ return TOP_LAYER_SELECTORS.some((s) => {
46
+ try {
47
+ return el.matches(s);
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ });
53
+ }
54
+ /**
55
+ * Does this element establish a containing block for `position: fixed`
56
+ * descendants?
57
+ *
58
+ * Mirrors floating-ui's battle-tested `isContainingBlock`, with the same two
59
+ * deliberate omissions relative to a naive reading of MDN:
60
+ *
61
+ * - `filter`/`backdrop-filter` are ignored on WebKit: Safari historically does
62
+ * NOT form a fixed CB from them (plain `filter` was only fixed in Safari 26).
63
+ * Misdetecting a CB the browser doesn't honor would break correct layouts;
64
+ * missing one merely preserves the pre-CB-aware behavior.
65
+ * - `container-type` is NOT checked: the CSSWG removed layout containment from
66
+ * it (2024, csswg-drafts#10544) and Chrome 129+/Firefox/Safari all shipped
67
+ * the change, so container queries no longer re-parent fixed descendants.
68
+ */
69
+ export function isFixedContainingBlock(el) {
70
+ const s = getComputedStyle(el);
71
+ return (notNone(s.transform) ||
72
+ notNone(s.translate) ||
73
+ notNone(s.rotate) ||
74
+ notNone(s.scale) ||
75
+ notNone(s.perspective) ||
76
+ (!isWebKit() && (notNone(s.filter) || notNone(s.backdropFilter))) ||
77
+ s.contentVisibility === "auto" ||
78
+ WILL_CHANGE_RE.test(s.willChange) ||
79
+ CONTAIN_RE.test(s.contain));
80
+ }
81
+ /**
82
+ * The nearest ancestor of `el` that establishes a containing block for
83
+ * `position: fixed` descendants, or `null` when fixed descendants resolve
84
+ * against the viewport. The walk stops (returning `null`) at top-layer
85
+ * elements — a modal `<dialog>`, an open `[popover]`, a fullscreen element —
86
+ * since the top layer escapes every ancestor containing block by design
87
+ * (unless such an element is itself CB-forming, e.g. a transformed dialog).
88
+ */
89
+ export function fixedContainingBlockAncestor(el) {
90
+ for (let n = el.parentElement; n; n = n.parentElement) {
91
+ if (isFixedContainingBlock(n))
92
+ return n;
93
+ if (isTopLayer(n))
94
+ return null;
95
+ }
96
+ return null;
97
+ }
98
+ /**
99
+ * The rect that `position: fixed` descendants of `el` actually resolve
100
+ * against, in viewport (visual) coordinates.
101
+ *
102
+ * Walks up from `el`'s parent looking for the nearest containing-block-forming
103
+ * ancestor (see {@link fixedContainingBlockAncestor}) and returns its padding
104
+ * box — per CSS, the CB is the padding box, not the border box. When no such
105
+ * ancestor exists (the overwhelmingly common case) it returns the viewport
106
+ * rect based on `window.innerWidth/innerHeight`, byte-identical to what the
107
+ * pre-CB-aware code measured.
108
+ *
109
+ * Known limitation: for a ROTATED CB ancestor the returned rect is the
110
+ * axis-aligned bounding box of the rotated element — consumers comparing
111
+ * rects (overflow checks, clamps) get an approximation there. Scaled
112
+ * ancestors are handled (border widths are converted to visual px).
113
+ */
114
+ export function fixedContainingBlockRect(el) {
115
+ const n = fixedContainingBlockAncestor(el);
116
+ if (n) {
117
+ const s = getComputedStyle(n);
118
+ const r = n.getBoundingClientRect();
119
+ // computed border widths are layout px; the rect is visual (post-
120
+ // transform) px — convert via the ancestor's accumulated scale so the
121
+ // inset is correct inside scaled wrappers too
122
+ const sx = n.offsetWidth ? r.width / n.offsetWidth : 1;
123
+ const sy = n.offsetHeight ? r.height / n.offsetHeight : 1;
124
+ const bl = (parseFloat(s.borderLeftWidth) || 0) * sx;
125
+ const bt = (parseFloat(s.borderTopWidth) || 0) * sy;
126
+ const br = (parseFloat(s.borderRightWidth) || 0) * sx;
127
+ const bb = (parseFloat(s.borderBottomWidth) || 0) * sy;
128
+ return new DOMRectReadOnly(r.left + bl, r.top + bt, r.width - bl - br, r.height - bt - bb);
129
+ }
130
+ return new DOMRectReadOnly(0, 0, window.innerWidth, window.innerHeight);
131
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Shared bits for overlay actions (`popover`, `spotlight`, `dimBehind`) that
3
+ * can portal their DOM into a consumer-provided container instead of
4
+ * `document.body`.
5
+ */
6
+ /**
7
+ * The `container` option shape: a concrete element, or a lazy factory (useful
8
+ * when the element does not exist yet at action-setup time). A factory
9
+ * returning `null` means "use the default".
10
+ */
11
+ export type OverlayContainerOption = HTMLElement | (() => HTMLElement | null);
12
+ /**
13
+ * Resolve a `container` option to an element, or `null` when unset (callers
14
+ * then fall back to their default — typically `document.body`).
15
+ */
16
+ export declare function resolveContainerOption(option: OverlayContainerOption | undefined | null): HTMLElement | null;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Shared bits for overlay actions (`popover`, `spotlight`, `dimBehind`) that
3
+ * can portal their DOM into a consumer-provided container instead of
4
+ * `document.body`.
5
+ */
6
+ /**
7
+ * Resolve a `container` option to an element, or `null` when unset (callers
8
+ * then fall back to their default — typically `document.body`).
9
+ */
10
+ export function resolveContainerOption(option) {
11
+ return (typeof option === "function" ? option() : option) ?? null;
12
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "3.151.0",
3
+ "version": "3.152.0",
4
4
  "packageManager": "pnpm@11.5.0",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -136,15 +136,15 @@
136
136
  "@codemirror/view": "^6.43.8",
137
137
  "@eslint/js": "^9.39.5",
138
138
  "@marianmeres/random-human-readable": "^1.10.2",
139
- "@milkdown/core": "^7.22.0",
140
- "@milkdown/ctx": "^7.22.0",
141
- "@milkdown/plugin-history": "^7.22.0",
142
- "@milkdown/plugin-listener": "^7.22.0",
143
- "@milkdown/preset-commonmark": "^7.22.0",
144
- "@milkdown/preset-gfm": "^7.22.0",
145
- "@milkdown/prose": "^7.22.0",
146
- "@milkdown/transformer": "^7.22.0",
147
- "@milkdown/utils": "^7.22.0",
139
+ "@milkdown/core": "^7.22.1",
140
+ "@milkdown/ctx": "^7.22.1",
141
+ "@milkdown/plugin-history": "^7.22.1",
142
+ "@milkdown/plugin-listener": "^7.22.1",
143
+ "@milkdown/preset-commonmark": "^7.22.1",
144
+ "@milkdown/preset-gfm": "^7.22.1",
145
+ "@milkdown/prose": "^7.22.1",
146
+ "@milkdown/transformer": "^7.22.1",
147
+ "@milkdown/utils": "^7.22.1",
148
148
  "@sveltejs/adapter-auto": "^4.0.0",
149
149
  "@sveltejs/kit": "^2.70.2",
150
150
  "@sveltejs/package": "^2.5.8",
@@ -162,8 +162,8 @@
162
162
  "prettier": "^3.9.6",
163
163
  "prettier-plugin-svelte": "^3.5.2",
164
164
  "publint": "^0.3.23",
165
- "svelte": "^5.56.8",
166
- "svelte-check": "^4.7.5",
165
+ "svelte": "^5.56.9",
166
+ "svelte-check": "^4.7.6",
167
167
  "tailwindcss": "^4.3.3",
168
168
  "tsx": "^4.23.12",
169
169
  "typescript": "^5.9.3",