mnfst 0.5.130 → 0.5.133
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/lib/manifest.css +25 -16
- package/lib/manifest.data.js +33 -6
- package/lib/manifest.dropdown.css +11 -10
- package/lib/manifest.dropdowns.js +70 -40
- package/lib/manifest.form.css +17 -9
- package/lib/manifest.integrity.json +7 -7
- package/lib/manifest.js +26 -2
- package/lib/manifest.markdown.js +12 -2
- package/lib/manifest.min.css +1 -1
- package/lib/manifest.sidebar.css +4 -4
- package/lib/manifest.svg.js +5 -1
- package/lib/manifest.toast.css +1 -1
- package/lib/manifest.tooltip.css +4 -4
- package/lib/manifest.tooltips.js +16 -4
- package/lib/manifest.utilities.js +31 -0
- package/package.json +3 -2
package/lib/manifest.css
CHANGED
|
@@ -2142,16 +2142,17 @@
|
|
|
2142
2142
|
border-radius: 6px;
|
|
2143
2143
|
cursor: pointer;
|
|
2144
2144
|
user-select: none;
|
|
2145
|
+
transition: none;
|
|
2145
2146
|
|
|
2146
2147
|
&:hover {
|
|
2147
2148
|
color: var(--color-field-inverse, oklch(43.9% 0 0));
|
|
2148
2149
|
text-decoration: inherit;
|
|
2149
|
-
background-color: var(--color-field-surface, color-mix(oklch(20.5% 0 0) 10%, transparent))
|
|
2150
|
+
background-color: var(--color-field-surface, color-mix(oklch(20.5% 0 0) 10%, transparent))
|
|
2150
2151
|
}
|
|
2151
2152
|
|
|
2152
2153
|
&:active {
|
|
2153
2154
|
color: var(--color-field-inverse, oklch(43.9% 0 0));
|
|
2154
|
-
background-color: var(--color-field-surface, color-mix(oklch(20.5% 0 0) 10%, transparent))
|
|
2155
|
+
background-color: var(--color-field-surface, color-mix(oklch(20.5% 0 0) 10%, transparent))
|
|
2155
2156
|
}
|
|
2156
2157
|
|
|
2157
2158
|
& span,
|
|
@@ -2168,7 +2169,7 @@
|
|
|
2168
2169
|
/* Titles */
|
|
2169
2170
|
& small {
|
|
2170
2171
|
padding: 0.25rem 0.5rem;
|
|
2171
|
-
color: var(--color-content-neutral, oklch(43.9% 0 0))
|
|
2172
|
+
color: var(--color-content-neutral, oklch(43.9% 0 0))
|
|
2172
2173
|
}
|
|
2173
2174
|
|
|
2174
2175
|
/* Horizontal rules (offset to ignore menu padding) */
|
|
@@ -2178,7 +2179,7 @@
|
|
|
2178
2179
|
margin-inline-start: calc(0.25rem * -1);
|
|
2179
2180
|
margin-top: 0.25rem;
|
|
2180
2181
|
margin-bottom: 0.25rem;
|
|
2181
|
-
background-color: var(--color-line, color-mix(oklch(20.5% 0 0) 10%, transparent))
|
|
2182
|
+
background-color: var(--color-line, color-mix(oklch(20.5% 0 0) 10%, transparent))
|
|
2182
2183
|
}
|
|
2183
2184
|
|
|
2184
2185
|
/* Labels */
|
|
@@ -2226,7 +2227,7 @@
|
|
|
2226
2227
|
|
|
2227
2228
|
/* Dark theme */
|
|
2228
2229
|
:where(.dark menu[popover]) :where(li, a, button, label):hover {
|
|
2229
|
-
background-color: var(--color-field-surface-hover, color-mix(oklch(20.5% 0 0) 15%, transparent))
|
|
2230
|
+
background-color: var(--color-field-surface-hover, color-mix(oklch(20.5% 0 0) 15%, transparent))
|
|
2230
2231
|
}
|
|
2231
2232
|
|
|
2232
2233
|
/* Nested menu alignment */
|
|
@@ -2237,7 +2238,7 @@
|
|
|
2237
2238
|
|
|
2238
2239
|
/* Center alignment */
|
|
2239
2240
|
:where(menu.center) {
|
|
2240
|
-
position-area: center
|
|
2241
|
+
position-area: center
|
|
2241
2242
|
}
|
|
2242
2243
|
|
|
2243
2244
|
/* Top alignment */
|
|
@@ -2374,6 +2375,7 @@
|
|
|
2374
2375
|
z-index: 1
|
|
2375
2376
|
}
|
|
2376
2377
|
|
|
2378
|
+
/* Equal-width children (give the wrapper a width if children need more room) */
|
|
2377
2379
|
&.even>* {
|
|
2378
2380
|
flex-shrink: initial;
|
|
2379
2381
|
width: 100%
|
|
@@ -2415,21 +2417,24 @@
|
|
|
2415
2417
|
/* Concentric corners */
|
|
2416
2418
|
&>* {
|
|
2417
2419
|
flex-grow: 1;
|
|
2420
|
+
width: fit-content;
|
|
2418
2421
|
min-height: 0;
|
|
2419
2422
|
height: 100%;
|
|
2423
|
+
color: var(--color-content-neutral, oklch(43.9% 0 0));
|
|
2420
2424
|
background-color: transparent;
|
|
2421
2425
|
border-radius: max(calc(var(--radius, 0.5rem) - var(--spacing, 0.25rem) / 2), 0px);
|
|
2422
2426
|
|
|
2423
2427
|
&:hover:not(.selected, [aria-selected=true], [aria-current]) {
|
|
2424
|
-
|
|
2428
|
+
color: var(--color-field-inverse, oklch(20.5% 0 0));
|
|
2429
|
+
background-color: color-mix(in oklch, var(--color-field-surface-hover, oklch(70.8% 0 0)) 40%, transparent)
|
|
2425
2430
|
}
|
|
2426
2431
|
}
|
|
2427
2432
|
|
|
2428
2433
|
/* Selected tab */
|
|
2429
2434
|
&>:is(.selected, [aria-selected=true], [aria-current]) {
|
|
2430
|
-
color: var(--color-
|
|
2431
|
-
background-color: var(--color-
|
|
2432
|
-
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)
|
|
2435
|
+
color: var(--color-field-inverse, oklch(20.5% 0 0));
|
|
2436
|
+
background-color: var(--color-field-surface, oklch(87% 0 0));
|
|
2437
|
+
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)
|
|
2433
2438
|
}
|
|
2434
2439
|
|
|
2435
2440
|
/* Background slider for selected tab) */
|
|
@@ -2440,7 +2445,7 @@
|
|
|
2440
2445
|
anchor-name: --selected-tab;
|
|
2441
2446
|
--co-anchor: --selected-tab;
|
|
2442
2447
|
background-color: transparent;
|
|
2443
|
-
box-shadow: none
|
|
2448
|
+
box-shadow: none
|
|
2444
2449
|
}
|
|
2445
2450
|
|
|
2446
2451
|
&:has(>:is(.selected, [aria-selected=true], [aria-current]))::before {
|
|
@@ -2452,10 +2457,14 @@
|
|
|
2452
2457
|
left: anchor(left);
|
|
2453
2458
|
width: anchor-size(width);
|
|
2454
2459
|
height: anchor-size(height);
|
|
2455
|
-
background-color: var(--color-
|
|
2460
|
+
background-color: var(--color-page, oklch(98.5% 0 0));
|
|
2456
2461
|
border-radius: max(calc(var(--radius, 0.5rem) - var(--spacing, 0.25rem) / 2), 0px);
|
|
2457
2462
|
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
|
2458
|
-
transition: var(--tab-slide-transition, all 0.15s ease-in-out)
|
|
2463
|
+
transition: var(--tab-slide-transition, all 0.15s ease-in-out)
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
.dark &:has(>:is(.selected, [aria-selected=true], [aria-current]))::before {
|
|
2467
|
+
background-color: var(--color-field-surface, oklch(87% 0 0))
|
|
2459
2468
|
}
|
|
2460
2469
|
|
|
2461
2470
|
/* Track the tab rigidly during continuous reflow (drag-to-resize, window resize) */
|
|
@@ -2482,7 +2491,7 @@
|
|
|
2482
2491
|
width: 100%;
|
|
2483
2492
|
|
|
2484
2493
|
&:has([type=radio], [type=checkbox]) {
|
|
2485
|
-
gap: calc(var(--spacing, 0.25rem) * 2)
|
|
2494
|
+
gap: calc(var(--spacing, 0.25rem) * 2)
|
|
2486
2495
|
}
|
|
2487
2496
|
}
|
|
2488
2497
|
|
|
@@ -2496,7 +2505,7 @@
|
|
|
2496
2505
|
& :where(legend) {
|
|
2497
2506
|
padding: 0 1.5ch;
|
|
2498
2507
|
font-size: 0.875rem;
|
|
2499
|
-
color: var(--color-content-subtle, oklch(55.6% 0 0))
|
|
2508
|
+
color: var(--color-content-subtle, oklch(55.6% 0 0))
|
|
2500
2509
|
}
|
|
2501
2510
|
}
|
|
2502
2511
|
|
|
@@ -3550,7 +3559,7 @@
|
|
|
3550
3559
|
/* Entry animation */
|
|
3551
3560
|
:where(.toast-entry) {
|
|
3552
3561
|
opacity: 1;
|
|
3553
|
-
transform: translateY(0)
|
|
3562
|
+
transform: translateY(0)
|
|
3554
3563
|
}
|
|
3555
3564
|
|
|
3556
3565
|
/* Exit animation */
|
package/lib/manifest.data.js
CHANGED
|
@@ -27,19 +27,38 @@ async function ensureManifest() {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
//
|
|
30
|
+
// Interpolate ${VAR} placeholders in a string against window.env. Only
|
|
31
|
+
// PUBLIC_-prefixed vars reach window.env (the mnfst-run dev server filters
|
|
32
|
+
// .env by that prefix to keep server-side secrets like MANIFEST_API_KEY out
|
|
33
|
+
// of the browser). Misses warn once per name so a missing var doesn't fail
|
|
34
|
+
// silently downstream as an empty URL / endpoint.
|
|
35
|
+
const _warnedMissingEnv = new Set();
|
|
31
36
|
function interpolateEnvVars(str) {
|
|
32
37
|
if (typeof str !== 'string') return str;
|
|
33
38
|
return str.replace(/\$\{([^}]+)\}/g, (match, varName) => {
|
|
34
|
-
// Check for environment variables (in browser, these would be set by build process)
|
|
35
39
|
if (typeof process !== 'undefined' && process.env && process.env[varName]) {
|
|
36
40
|
return process.env[varName];
|
|
37
41
|
}
|
|
38
|
-
// Check for window.env (common pattern for client-side env vars)
|
|
39
42
|
if (typeof window !== 'undefined' && window.env && window.env[varName]) {
|
|
40
43
|
return window.env[varName];
|
|
41
44
|
}
|
|
42
|
-
|
|
45
|
+
if (!_warnedMissingEnv.has(varName)) {
|
|
46
|
+
_warnedMissingEnv.add(varName);
|
|
47
|
+
if (!varName.startsWith('PUBLIC_')) {
|
|
48
|
+
console.warn(
|
|
49
|
+
`[Manifest Data] data source references \${${varName}}, but only ` +
|
|
50
|
+
`PUBLIC_-prefixed env vars are injected into window.env by mnfst-run. ` +
|
|
51
|
+
`Rename to PUBLIC_${varName}, hardcode the value, or supply it via ` +
|
|
52
|
+
`<script>window.env = {…}</script>. Leaving placeholder literal.`
|
|
53
|
+
);
|
|
54
|
+
} else {
|
|
55
|
+
console.warn(
|
|
56
|
+
`[Manifest Data] data source references \${${varName}}, but it is not ` +
|
|
57
|
+
`present in window.env. Add ${varName}=… to .env (read by mnfst-run) ` +
|
|
58
|
+
`or set it via <script>window.env = {…}</script>. Leaving placeholder literal.`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
43
62
|
return match;
|
|
44
63
|
});
|
|
45
64
|
}
|
|
@@ -1297,7 +1316,11 @@ async function loadYamlLibrary() {
|
|
|
1297
1316
|
|
|
1298
1317
|
yamlLoadingPromise = new Promise((resolve, reject) => {
|
|
1299
1318
|
const script = document.createElement('script');
|
|
1300
|
-
|
|
1319
|
+
// Pinned + Subresource Integrity — a floating version or tampered CDN
|
|
1320
|
+
// file can't inject arbitrary JS into the user's page.
|
|
1321
|
+
script.src = 'https://cdn.jsdelivr.net/npm/js-yaml@4.2.0/dist/js-yaml.min.js';
|
|
1322
|
+
script.integrity = 'sha384-hyhT0yrWXjngXhDa5wpJTU0pt/Djmlx4KtZECqqS6ZHf3ah4fWkFeW/2eWX9cX5J';
|
|
1323
|
+
script.crossOrigin = 'anonymous';
|
|
1301
1324
|
script.onload = () => {
|
|
1302
1325
|
if (typeof window.jsyaml !== 'undefined') {
|
|
1303
1326
|
jsyaml = window.jsyaml;
|
|
@@ -1332,7 +1355,11 @@ async function loadCSVParser() {
|
|
|
1332
1355
|
|
|
1333
1356
|
csvLoadingPromise = new Promise((resolve, reject) => {
|
|
1334
1357
|
const script = document.createElement('script');
|
|
1335
|
-
|
|
1358
|
+
// Pinned + Subresource Integrity — a floating version or tampered CDN
|
|
1359
|
+
// file can't inject arbitrary JS into the user's page.
|
|
1360
|
+
script.src = 'https://cdn.jsdelivr.net/npm/papaparse@5.5.3/papaparse.min.js';
|
|
1361
|
+
script.integrity = 'sha384-Jd2/X5FXVKahwaY2nivvw4LfSTg9idSj8yNXWtT4qef0fHSYr6M7M8bJAfbFYoMc';
|
|
1362
|
+
script.crossOrigin = 'anonymous';
|
|
1336
1363
|
script.onload = () => {
|
|
1337
1364
|
if (typeof window.Papa !== 'undefined') {
|
|
1338
1365
|
papaparse = window.Papa;
|
|
@@ -6,19 +6,19 @@
|
|
|
6
6
|
|
|
7
7
|
@layer base {
|
|
8
8
|
|
|
9
|
-
@keyframes
|
|
9
|
+
@keyframes popover-in {
|
|
10
10
|
from {
|
|
11
|
-
transform: scale(.9)
|
|
11
|
+
transform: scale(.9)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
to {
|
|
15
|
-
transform: none
|
|
15
|
+
transform: none
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
:where([popover]):not(.unstyle):popover-open {
|
|
20
20
|
display: flex;
|
|
21
|
-
animation:
|
|
21
|
+
animation: popover-in .15s ease-in both
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -66,16 +66,17 @@
|
|
|
66
66
|
border-radius: 6px;
|
|
67
67
|
cursor: pointer;
|
|
68
68
|
user-select: none;
|
|
69
|
+
transition: none;
|
|
69
70
|
|
|
70
71
|
&:hover {
|
|
71
72
|
color: var(--color-field-inverse, oklch(43.9% 0 0));
|
|
72
73
|
text-decoration: inherit;
|
|
73
|
-
background-color: var(--color-field-surface, color-mix(oklch(20.5% 0 0) 10%, transparent))
|
|
74
|
+
background-color: var(--color-field-surface, color-mix(oklch(20.5% 0 0) 10%, transparent))
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
&:active {
|
|
77
78
|
color: var(--color-field-inverse, oklch(43.9% 0 0));
|
|
78
|
-
background-color: var(--color-field-surface, color-mix(oklch(20.5% 0 0) 10%, transparent))
|
|
79
|
+
background-color: var(--color-field-surface, color-mix(oklch(20.5% 0 0) 10%, transparent))
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
& span,
|
|
@@ -92,7 +93,7 @@
|
|
|
92
93
|
/* Titles */
|
|
93
94
|
& small {
|
|
94
95
|
padding: 0.25rem 0.5rem;
|
|
95
|
-
color: var(--color-content-neutral, oklch(43.9% 0 0))
|
|
96
|
+
color: var(--color-content-neutral, oklch(43.9% 0 0))
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
/* Horizontal rules (offset to ignore menu padding) */
|
|
@@ -102,7 +103,7 @@
|
|
|
102
103
|
margin-inline-start: calc(0.25rem * -1);
|
|
103
104
|
margin-top: 0.25rem;
|
|
104
105
|
margin-bottom: 0.25rem;
|
|
105
|
-
background-color: var(--color-line, color-mix(oklch(20.5% 0 0) 10%, transparent))
|
|
106
|
+
background-color: var(--color-line, color-mix(oklch(20.5% 0 0) 10%, transparent))
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
/* Labels */
|
|
@@ -150,7 +151,7 @@
|
|
|
150
151
|
|
|
151
152
|
/* Dark theme */
|
|
152
153
|
:where(.dark menu[popover]) :where(li, a, button, label):hover {
|
|
153
|
-
background-color: var(--color-field-surface-hover, color-mix(oklch(20.5% 0 0) 15%, transparent))
|
|
154
|
+
background-color: var(--color-field-surface-hover, color-mix(oklch(20.5% 0 0) 15%, transparent))
|
|
154
155
|
}
|
|
155
156
|
|
|
156
157
|
/* Nested menu alignment */
|
|
@@ -161,7 +162,7 @@
|
|
|
161
162
|
|
|
162
163
|
/* Center alignment */
|
|
163
164
|
:where(menu.center) {
|
|
164
|
-
position-area: center
|
|
165
|
+
position-area: center
|
|
165
166
|
}
|
|
166
167
|
|
|
167
168
|
/* Top alignment */
|
|
@@ -175,6 +175,17 @@ function initializeDropdownPlugin() {
|
|
|
175
175
|
el.style.setProperty('--trigger-anchor', anchorName);
|
|
176
176
|
menu.style.setProperty('position-anchor', anchorName);
|
|
177
177
|
|
|
178
|
+
// Re-point the shared menu at whichever trigger opens it (multi-trigger)
|
|
179
|
+
const pointMenuHere = () => {
|
|
180
|
+
const a = el.style.getPropertyValue('--trigger-anchor');
|
|
181
|
+
if (a) menu.style.setProperty('position-anchor', a);
|
|
182
|
+
};
|
|
183
|
+
if (!modifiers.includes('context') && !el.__mnfstAnchorSwapBound) {
|
|
184
|
+
el.__mnfstAnchorSwapBound = true;
|
|
185
|
+
el.addEventListener('pointerdown', pointMenuHere);
|
|
186
|
+
el.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') pointMenuHere(); });
|
|
187
|
+
}
|
|
188
|
+
|
|
178
189
|
// ----- A11y wiring (WAI-ARIA Menu Button pattern) -----
|
|
179
190
|
// The trigger needs `aria-haspopup="menu"`, `aria-controls`, and a
|
|
180
191
|
// dynamic `aria-expanded` that follows the popover's open state.
|
|
@@ -192,11 +203,12 @@ function initializeDropdownPlugin() {
|
|
|
192
203
|
menu.querySelectorAll('li').forEach((li) => {
|
|
193
204
|
if (!li.hasAttribute('role')) li.setAttribute('role', 'menuitem');
|
|
194
205
|
});
|
|
195
|
-
// Keep aria-expanded in sync
|
|
206
|
+
// Keep aria-expanded in sync across every trigger of this menu.
|
|
196
207
|
if (!menu.__mnfstAriaToggleBound) {
|
|
197
208
|
menu.__mnfstAriaToggleBound = true;
|
|
198
209
|
menu.addEventListener('toggle', (e) => {
|
|
199
|
-
|
|
210
|
+
const expanded = e.newState === 'open' ? 'true' : 'false';
|
|
211
|
+
document.querySelectorAll(`[aria-controls="${menu.id}"]`).forEach(t => t.setAttribute('aria-expanded', expanded));
|
|
200
212
|
});
|
|
201
213
|
}
|
|
202
214
|
}
|
|
@@ -208,6 +220,7 @@ function initializeDropdownPlugin() {
|
|
|
208
220
|
clearTimeout(hoverTimeout);
|
|
209
221
|
clearTimeout(autoCloseTimeout);
|
|
210
222
|
|
|
223
|
+
pointMenuHere();
|
|
211
224
|
menu.showPopover();
|
|
212
225
|
}
|
|
213
226
|
};
|
|
@@ -244,46 +257,63 @@ function initializeDropdownPlugin() {
|
|
|
244
257
|
if (menu.matches(':popover-open')) menu.hidePopover();
|
|
245
258
|
};
|
|
246
259
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if (
|
|
271
|
-
|
|
260
|
+
// Bind the right-click trigger once per element. Without
|
|
261
|
+
// the guard, reprocessing (router re-walks, re-init) would
|
|
262
|
+
// stack duplicate handlers.
|
|
263
|
+
if (!el.__mnfstContextTriggerBound) {
|
|
264
|
+
el.__mnfstContextTriggerBound = true;
|
|
265
|
+
el.addEventListener('contextmenu', (e) => {
|
|
266
|
+
e.preventDefault();
|
|
267
|
+
e.stopPropagation();
|
|
268
|
+
|
|
269
|
+
// Stash the actual right-clicked element so menu items can act on it.
|
|
270
|
+
// (e.target is the deepest hit; el is the directive-bearing ancestor.)
|
|
271
|
+
menu._triggerEl = e.target.closest('[data-cp-value], [x-data]') || el;
|
|
272
|
+
menu._triggerHost = el;
|
|
273
|
+
|
|
274
|
+
// Position at cursor, overriding anchor positioning
|
|
275
|
+
menu.style.position = 'fixed';
|
|
276
|
+
menu.style.positionAnchor = 'unset';
|
|
277
|
+
menu.style.positionArea = 'unset';
|
|
278
|
+
menu.style.inset = 'auto';
|
|
279
|
+
menu.style.left = e.clientX + 'px';
|
|
280
|
+
menu.style.top = e.clientY + 'px';
|
|
281
|
+
menu.style.margin = '0';
|
|
282
|
+
|
|
283
|
+
if (!menu.matches(':popover-open')) menu.showPopover();
|
|
284
|
+
|
|
285
|
+
// Adjust if menu overflows viewport
|
|
286
|
+
requestAnimationFrame(() => {
|
|
287
|
+
const rect = menu.getBoundingClientRect();
|
|
288
|
+
if (rect.right > window.innerWidth) menu.style.left = (window.innerWidth - rect.width) + 'px';
|
|
289
|
+
if (rect.bottom > window.innerHeight) menu.style.top = (window.innerHeight - rect.height) + 'px';
|
|
290
|
+
});
|
|
272
291
|
});
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Manual dismiss: click outside or Escape
|
|
276
|
-
document.addEventListener('pointerdown', (e) => {
|
|
277
|
-
if (menu.matches(':popover-open') && !menu.contains(e.target)) closeContext();
|
|
278
|
-
});
|
|
279
|
-
document.addEventListener('keydown', (e) => {
|
|
280
|
-
if (e.key === 'Escape' && menu.matches(':popover-open')) { closeContext(); el.focus(); }
|
|
281
|
-
});
|
|
292
|
+
}
|
|
282
293
|
|
|
283
|
-
//
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
294
|
+
// Document-level dismiss listeners are GLOBAL — re-binding
|
|
295
|
+
// them on every reprocess leaks listeners on `document`.
|
|
296
|
+
// Bind once per menu, tied to an AbortController so they
|
|
297
|
+
// share one removable signal.
|
|
298
|
+
if (!menu.__mnfstContextDismissBound) {
|
|
299
|
+
menu.__mnfstContextDismissBound = true;
|
|
300
|
+
const ctxAbort = new AbortController();
|
|
301
|
+
menu.__mnfstContextAbort = ctxAbort;
|
|
302
|
+
const { signal } = ctxAbort;
|
|
303
|
+
|
|
304
|
+
// Manual dismiss: click outside or Escape
|
|
305
|
+
document.addEventListener('pointerdown', (e) => {
|
|
306
|
+
if (menu.matches(':popover-open') && !menu.contains(e.target)) closeContext();
|
|
307
|
+
}, { signal });
|
|
308
|
+
document.addEventListener('keydown', (e) => {
|
|
309
|
+
if (e.key === 'Escape' && menu.matches(':popover-open')) { closeContext(); el.focus(); }
|
|
310
|
+
}, { signal });
|
|
311
|
+
|
|
312
|
+
// Close after clicking a menu item
|
|
313
|
+
menu.addEventListener('click', (e) => {
|
|
314
|
+
if (e.target.closest('li, a, button')) closeContext();
|
|
315
|
+
}, { signal });
|
|
316
|
+
}
|
|
287
317
|
}
|
|
288
318
|
|
|
289
319
|
// Add keyboard navigation handling
|
package/lib/manifest.form.css
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
z-index: 1
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/* Equal-width children (give the wrapper a width if children need more room) */
|
|
19
20
|
&.even>* {
|
|
20
21
|
flex-shrink: initial;
|
|
21
22
|
width: 100%
|
|
@@ -57,21 +58,24 @@
|
|
|
57
58
|
/* Concentric corners */
|
|
58
59
|
&>* {
|
|
59
60
|
flex-grow: 1;
|
|
61
|
+
width: fit-content;
|
|
60
62
|
min-height: 0;
|
|
61
63
|
height: 100%;
|
|
64
|
+
color: var(--color-content-neutral, oklch(43.9% 0 0));
|
|
62
65
|
background-color: transparent;
|
|
63
66
|
border-radius: max(calc(var(--radius, 0.5rem) - var(--spacing, 0.25rem) / 2), 0px);
|
|
64
67
|
|
|
65
68
|
&:hover:not(.selected, [aria-selected=true], [aria-current]) {
|
|
66
|
-
|
|
69
|
+
color: var(--color-field-inverse, oklch(20.5% 0 0));
|
|
70
|
+
background-color: color-mix(in oklch, var(--color-field-surface-hover, oklch(70.8% 0 0)) 40%, transparent)
|
|
67
71
|
}
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
/* Selected tab */
|
|
71
75
|
&>:is(.selected, [aria-selected=true], [aria-current]) {
|
|
72
|
-
color: var(--color-
|
|
73
|
-
background-color: var(--color-
|
|
74
|
-
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)
|
|
76
|
+
color: var(--color-field-inverse, oklch(20.5% 0 0));
|
|
77
|
+
background-color: var(--color-field-surface, oklch(87% 0 0));
|
|
78
|
+
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
/* Background slider for selected tab) */
|
|
@@ -82,7 +86,7 @@
|
|
|
82
86
|
anchor-name: --selected-tab;
|
|
83
87
|
--co-anchor: --selected-tab;
|
|
84
88
|
background-color: transparent;
|
|
85
|
-
box-shadow: none
|
|
89
|
+
box-shadow: none
|
|
86
90
|
}
|
|
87
91
|
|
|
88
92
|
&:has(>:is(.selected, [aria-selected=true], [aria-current]))::before {
|
|
@@ -94,10 +98,14 @@
|
|
|
94
98
|
left: anchor(left);
|
|
95
99
|
width: anchor-size(width);
|
|
96
100
|
height: anchor-size(height);
|
|
97
|
-
background-color: var(--color-
|
|
101
|
+
background-color: var(--color-page, oklch(98.5% 0 0));
|
|
98
102
|
border-radius: max(calc(var(--radius, 0.5rem) - var(--spacing, 0.25rem) / 2), 0px);
|
|
99
103
|
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
|
100
|
-
transition: var(--tab-slide-transition, all 0.15s ease-in-out)
|
|
104
|
+
transition: var(--tab-slide-transition, all 0.15s ease-in-out)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.dark &:has(>:is(.selected, [aria-selected=true], [aria-current]))::before {
|
|
108
|
+
background-color: var(--color-field-surface, oklch(87% 0 0))
|
|
101
109
|
}
|
|
102
110
|
|
|
103
111
|
/* Track the tab rigidly during continuous reflow (drag-to-resize, window resize) */
|
|
@@ -124,7 +132,7 @@
|
|
|
124
132
|
width: 100%;
|
|
125
133
|
|
|
126
134
|
&:has([type=radio], [type=checkbox]) {
|
|
127
|
-
gap: calc(var(--spacing, 0.25rem) * 2)
|
|
135
|
+
gap: calc(var(--spacing, 0.25rem) * 2)
|
|
128
136
|
}
|
|
129
137
|
}
|
|
130
138
|
|
|
@@ -138,7 +146,7 @@
|
|
|
138
146
|
& :where(legend) {
|
|
139
147
|
padding: 0 1.5ch;
|
|
140
148
|
font-size: 0.875rem;
|
|
141
|
-
color: var(--color-content-subtle, oklch(55.6% 0 0))
|
|
149
|
+
color: var(--color-content-subtle, oklch(55.6% 0 0))
|
|
142
150
|
}
|
|
143
151
|
}
|
|
144
152
|
|
|
@@ -7,24 +7,24 @@
|
|
|
7
7
|
"manifest.color.js": "sha384-6Rv3LxyTcZNjrhtayQfqRdCx0uSZ4BiEbgEI98I62eTvp8Aw7LBIoNJ0Je1oktwL",
|
|
8
8
|
"manifest.colorpicker.js": "sha384-Wqz0ZIbeIi7KarqqqSLsQk+7E/fMaKhb32hrq5/eWzX1yjqMrpPZKH8y+jZ3mfg+",
|
|
9
9
|
"manifest.components.js": "sha384-73CB1A+LAGfNexkd7aT69APFSHMzix8irse9uzOkYehHHio4px3oR8JHJeaMH+jI",
|
|
10
|
-
"manifest.data.js": "sha384-
|
|
10
|
+
"manifest.data.js": "sha384-XDL6cgiej9q+11HT8cp/feCcq1ir0rq3FZYgWngW627DPQOSdmMQVy4sDK+j1qAm",
|
|
11
11
|
"manifest.datepicker.js": "sha384-NEb/H4vuR3CFtRcodHsm3jJjrcYW2JMpDlQKlgwTrzpMMTcDkFKYXzAYJD0gZ7Ov",
|
|
12
|
-
"manifest.dropdowns.js": "sha384-
|
|
12
|
+
"manifest.dropdowns.js": "sha384-a2j9Z1LWolyZANlfeBc1aIFQ0kf0UDeOZ8TetulYbpRS6T/zaD/OWSMGvz+gRehX",
|
|
13
13
|
"manifest.export.js": "sha384-RsTGzsPCBw3yO4+TdAGd4F+o3FnzUNlqnMBqtnn/kUfv7axpzRdPc2AnsExV/93c",
|
|
14
14
|
"manifest.icons.js": "sha384-uOkboYrovjCpl22eey3Jaxpey+pOnot5NDnRRumcRxiR7IOVaRh1i20gYnWXR5dW",
|
|
15
15
|
"manifest.localization.js": "sha384-M3HRb2Ma8PemfFeqq9rgWgw/+Vdb/8d5LGW2MFbVsXaWUPqr/mPuxWtl5Pv8wolL",
|
|
16
|
-
"manifest.markdown.js": "sha384-
|
|
16
|
+
"manifest.markdown.js": "sha384-IrtsTsNgCsKmrNQgnvrh5ETHdnrrsJeOg8r6zD5zfxEgXVUu+HZ6ai7e2iM9fXIM",
|
|
17
17
|
"manifest.payments.js": "sha384-Ng5y5hZfMqrSLo1AbQ8ucOni/BaaYsbEj16WW7rwsgRKEaflE+6lQRoZSlLA4PMd",
|
|
18
18
|
"manifest.resize.js": "sha384-S3v4RAJoA77LUH6MddYG9bx//dZX//up7XWeH69Ql2ge3bVHgC6mR/CJBe6y4nQI",
|
|
19
19
|
"manifest.router.js": "sha384-Kpl5k/pWOrMJhkgFZ9UAtGXuH7FDEv24WGMACpNqV8DW5ZjcbC67SnVY42qUSn3X",
|
|
20
20
|
"manifest.slides.js": "sha384-3uRTkyK9XPLmnxI2+igZlpi4EyPlU/7IHj5j3BZJJ2KN455vXyk99fiXV3feO/XY",
|
|
21
21
|
"manifest.status.js": "sha384-7cEl+Nh729ncqy5GtRYMqo5R4d257QPsoFm/hx9Znp9uV/D85pjxVzQ1fhiD+sO6",
|
|
22
|
-
"manifest.svg.js": "sha384-
|
|
22
|
+
"manifest.svg.js": "sha384-yAUSl5sTwyMSerR0zyuimuRNzySvKaGN9KuEBgu81MOjF1n5Y8PMtDfxUO6uHMHu",
|
|
23
23
|
"manifest.tabs.js": "sha384-7Kb1EHIbqh1NOl8J1NMp087lcF1gVmwm55QNM3s7JamV6sYiH/WZbdnknAZFtsfW",
|
|
24
24
|
"manifest.tailwind.js": "sha384-aHLvl2oSuUgy06VaBqhhByn5wWxqvnqxw6KCwehakKUS00F/s/Nb62umeASS6Y4P",
|
|
25
25
|
"manifest.toasts.js": "sha384-ytd5rDbax/Ou9z23uedFXPZbxDPsk2E/pxCTq4WLvfv+os1qTI6kELp0kPp07g24",
|
|
26
|
-
"manifest.tooltips.js": "sha384-
|
|
26
|
+
"manifest.tooltips.js": "sha384-ADzAx9D0HWq2b46mvNG05iOwPmEWdiFZNpEOXONSbBxs4xj1B/bzNL7S3x2R9cS1",
|
|
27
27
|
"manifest.url.parameters.js": "sha384-FIufiClqDx1rJpU/QUc9z/D43qClQ6Qm8rBahipbJl9BDHUvhrOsUDegmTWW7Tuf",
|
|
28
|
-
"manifest.utilities.js": "sha384-
|
|
29
|
-
"manifest.js": "sha384-
|
|
28
|
+
"manifest.utilities.js": "sha384-Hl2tlhHgIJLEr3CFiEq79nlWCgx6nSVJCyThPCZi3wOFBLv/KXm/VrIZ0vRZqBi7",
|
|
29
|
+
"manifest.js": "sha384-vc0GN2LMS0zDdT2Oj4a7NwyR498zKDgyiLiBl4VjDFJB+XDMcrdBRBBY5yn7ixXK"
|
|
30
30
|
}
|
package/lib/manifest.js
CHANGED
|
@@ -586,14 +586,38 @@
|
|
|
586
586
|
// the loader rather than borrowed from the data plugin because the
|
|
587
587
|
// data plugin's script may not have finished executing yet at the
|
|
588
588
|
// point we cache the manifest. window.env is populated by either
|
|
589
|
-
// the mnfst-run dev server (which reads
|
|
590
|
-
//
|
|
589
|
+
// the mnfst-run dev server (which reads PUBLIC_-prefixed vars from
|
|
590
|
+
// .env at startup) or a developer-supplied
|
|
591
|
+
// <script>window.env = {…}</script> block.
|
|
592
|
+
//
|
|
593
|
+
// Misses are warned, not silently dropped: a missing var almost
|
|
594
|
+
// always means the dev forgot the PUBLIC_ prefix or hasn't set the
|
|
595
|
+
// var at all, and an empty substitution downstream (e.g. an empty
|
|
596
|
+
// API URL) tends to fail far from the cause.
|
|
597
|
+
const warnedMissingEnv = new Set();
|
|
591
598
|
const interpolateManifestEnv = (obj) => {
|
|
592
599
|
if (obj === null || typeof obj !== 'object') return;
|
|
593
600
|
const subst = (str) => str.replace(/\$\{([^}]+)\}/g, (m, name) => {
|
|
594
601
|
if (typeof window !== 'undefined' && window.env && window.env[name] !== undefined) {
|
|
595
602
|
return window.env[name];
|
|
596
603
|
}
|
|
604
|
+
if (!warnedMissingEnv.has(name)) {
|
|
605
|
+
warnedMissingEnv.add(name);
|
|
606
|
+
if (!name.startsWith('PUBLIC_')) {
|
|
607
|
+
console.warn(
|
|
608
|
+
`[Manifest] manifest.json references \${${name}}, but only PUBLIC_-prefixed ` +
|
|
609
|
+
`env vars are injected into window.env by mnfst-run. Rename to ` +
|
|
610
|
+
`PUBLIC_${name}, hardcode the value, or supply it via ` +
|
|
611
|
+
`<script>window.env = {…}</script>. Leaving placeholder literal.`
|
|
612
|
+
);
|
|
613
|
+
} else {
|
|
614
|
+
console.warn(
|
|
615
|
+
`[Manifest] manifest.json references \${${name}}, but it is not present ` +
|
|
616
|
+
`in window.env. Add ${name}=… to .env (read by mnfst-run) or ` +
|
|
617
|
+
`set it via <script>window.env = {…}</script>. Leaving placeholder literal.`
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
597
621
|
return m;
|
|
598
622
|
});
|
|
599
623
|
const walk = (o) => {
|
package/lib/manifest.markdown.js
CHANGED
|
@@ -43,7 +43,13 @@ if (!window.ManifestDOMPurify) {
|
|
|
43
43
|
if (this._promise) return this._promise;
|
|
44
44
|
this._promise = new Promise((resolve, reject) => {
|
|
45
45
|
const script = document.createElement('script');
|
|
46
|
-
|
|
46
|
+
// Pinned + Subresource Integrity: a moving `@latest` (or a
|
|
47
|
+
// tampered CDN file) would otherwise run arbitrary JS in the
|
|
48
|
+
// user's page. The browser rejects the script if the bytes don't
|
|
49
|
+
// match the hash. Bump version AND integrity together.
|
|
50
|
+
script.src = 'https://cdn.jsdelivr.net/npm/dompurify@3.4.10/dist/purify.min.js';
|
|
51
|
+
script.integrity = 'sha384-eguRoJERj8ghOpzO//Rl7+ScQsQIR1cH+ajll7+fG+IpbNPlkZsQn9h8ccr+wPXx';
|
|
52
|
+
script.crossOrigin = 'anonymous';
|
|
47
53
|
script.onload = () => {
|
|
48
54
|
if (typeof window.DOMPurify !== 'undefined') {
|
|
49
55
|
resolve(window.DOMPurify);
|
|
@@ -96,7 +102,11 @@ async function loadMarkedJS() {
|
|
|
96
102
|
|
|
97
103
|
markedPromise = new Promise((resolve, reject) => {
|
|
98
104
|
const script = document.createElement('script');
|
|
99
|
-
|
|
105
|
+
// Pinned + Subresource Integrity (see DOMPurify loader above) — a
|
|
106
|
+
// floating version or tampered CDN file can't inject arbitrary JS.
|
|
107
|
+
script.src = 'https://cdn.jsdelivr.net/npm/marked@15.0.12/marked.min.js';
|
|
108
|
+
script.integrity = 'sha384-948ahk4ZmxYVYOc+rxN1H2gM1EJ2Duhp7uHtZ4WSLkV4Vtx5MUqnV+l7u9B+jFv+';
|
|
109
|
+
script.crossOrigin = 'anonymous';
|
|
100
110
|
script.onload = () => {
|
|
101
111
|
// Initialize marked.js
|
|
102
112
|
if (typeof marked !== 'undefined') {
|