mnfst 0.5.55 → 0.5.58
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.colorpicker.css +397 -0
- package/lib/manifest.js +1 -0
- package/lib/manifest.svg.js +228 -0
- package/package.json +1 -1
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/* Manifest Color Picker */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--icon-alpha-pattern: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 2 2'%3E%3Crect width='1' height='1' x='0' y='0' fill='%23808080' opacity='0.15'/%3E%3Crect width='1' height='1' x='1' y='1' fill='%23808080' opacity='0.15'/%3E%3C/svg%3E");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
@layer utilities {
|
|
8
|
+
|
|
9
|
+
/* Swatch trigger button */
|
|
10
|
+
:where(.colorpicker-swatch):not(.unstyle) {
|
|
11
|
+
position: relative;
|
|
12
|
+
width: var(--spacing-field-height, 2.25rem);
|
|
13
|
+
height: var(--spacing-field-height, 2.25rem);
|
|
14
|
+
min-width: var(--spacing-field-height, 2.25rem);
|
|
15
|
+
max-width: var(--spacing-field-height, 2.25rem);
|
|
16
|
+
padding: 0;
|
|
17
|
+
background: var(--swatch-color, #000000);
|
|
18
|
+
border-width: 1px;
|
|
19
|
+
border-style: solid;
|
|
20
|
+
border-color: oklch(from var(--swatch-color, #000000) calc(l + (0.5 - l) * 0.35) c calc(h + 0));
|
|
21
|
+
border-radius: var(--radius, 0.5rem);
|
|
22
|
+
cursor: pointer;
|
|
23
|
+
overflow: hidden;
|
|
24
|
+
transition: var(--transition);
|
|
25
|
+
|
|
26
|
+
&:hover,
|
|
27
|
+
&:active,
|
|
28
|
+
&:focus,
|
|
29
|
+
&:focus-visible {
|
|
30
|
+
border-color: oklch(from var(--swatch-color, #000000) calc(l + (0.5 - l) * 0.35) c calc(h + 0) / 0.5)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Alpha pattern background */
|
|
34
|
+
&::before {
|
|
35
|
+
content: "";
|
|
36
|
+
position: absolute;
|
|
37
|
+
top: 0;
|
|
38
|
+
left: 0;
|
|
39
|
+
width: 100%;
|
|
40
|
+
height: 100%;
|
|
41
|
+
z-index: -1;
|
|
42
|
+
background-image: var(--icon-alpha-pattern);
|
|
43
|
+
background-size: 50%;
|
|
44
|
+
background-position: top left
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Dropdown menu — unwrapped from :where() to beat base menu[popover] specificity */
|
|
49
|
+
[x-colorpicker]:not(.unstyle) {
|
|
50
|
+
|
|
51
|
+
/* Resetting dropdown styles */
|
|
52
|
+
& :where(hr) {
|
|
53
|
+
margin-top: 2px;
|
|
54
|
+
margin-bottom: 2px
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* & :where(li, a, button, label) {
|
|
58
|
+
width: fit-content;
|
|
59
|
+
min-width: var(--spacing-field-height, 2.25rem)
|
|
60
|
+
} */
|
|
61
|
+
|
|
62
|
+
/* Canvas wrapper */
|
|
63
|
+
& .canvas-wrapper {
|
|
64
|
+
position: relative;
|
|
65
|
+
width: 100%;
|
|
66
|
+
aspect-ratio: 1;
|
|
67
|
+
overflow: hidden;
|
|
68
|
+
cursor: crosshair;
|
|
69
|
+
touch-action: none
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Canvas (SV plane) */
|
|
73
|
+
& canvas {
|
|
74
|
+
display: block;
|
|
75
|
+
width: 100%;
|
|
76
|
+
height: 100%
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Reticle indicator */
|
|
80
|
+
& .color-reticle {
|
|
81
|
+
position: absolute;
|
|
82
|
+
width: 0.75rem;
|
|
83
|
+
height: 0.75rem;
|
|
84
|
+
z-index: 1;
|
|
85
|
+
border: 2px solid #ffffff;
|
|
86
|
+
border-radius: 50%;
|
|
87
|
+
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3), inset 0 0 0 1px rgba(0, 0, 0, 0.2);
|
|
88
|
+
transform: translate(-50%, -50%);
|
|
89
|
+
pointer-events: none;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Optional color controls container */
|
|
93
|
+
& .color-controls {
|
|
94
|
+
display: flex;
|
|
95
|
+
flex-direction: column;
|
|
96
|
+
gap: 0.75rem;
|
|
97
|
+
padding: 0.75rem
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Shared slider styles */
|
|
101
|
+
& input[type=range].hue,
|
|
102
|
+
& input[type=range].alpha {
|
|
103
|
+
width: 100%;
|
|
104
|
+
height: 0.75rem;
|
|
105
|
+
padding: 0;
|
|
106
|
+
border-radius: 1rem;
|
|
107
|
+
appearance: none;
|
|
108
|
+
-webkit-appearance: none;
|
|
109
|
+
cursor: pointer;
|
|
110
|
+
outline: none;
|
|
111
|
+
border: none;
|
|
112
|
+
background: transparent;
|
|
113
|
+
|
|
114
|
+
/* Webkit thumb */
|
|
115
|
+
&::-webkit-slider-thumb {
|
|
116
|
+
-webkit-appearance: none;
|
|
117
|
+
width: 1rem;
|
|
118
|
+
height: 1rem;
|
|
119
|
+
border-radius: 50%;
|
|
120
|
+
background: #ffffff;
|
|
121
|
+
border: 2px solid #ffffff;
|
|
122
|
+
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15), 0 1px 3px rgba(0, 0, 0, 0.25);
|
|
123
|
+
cursor: grab;
|
|
124
|
+
|
|
125
|
+
&:active {
|
|
126
|
+
cursor: grabbing;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* Firefox thumb */
|
|
131
|
+
&::-moz-range-thumb {
|
|
132
|
+
width: 1rem;
|
|
133
|
+
height: 1rem;
|
|
134
|
+
border-radius: 50%;
|
|
135
|
+
background: #ffffff;
|
|
136
|
+
border: 2px solid #ffffff;
|
|
137
|
+
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15), 0 1px 3px rgba(0, 0, 0, 0.25);
|
|
138
|
+
cursor: grab;
|
|
139
|
+
|
|
140
|
+
&:active {
|
|
141
|
+
cursor: grabbing;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* Webkit track */
|
|
146
|
+
&::-webkit-slider-runnable-track {
|
|
147
|
+
height: 0.75rem;
|
|
148
|
+
border-radius: 1rem
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* Firefox track */
|
|
152
|
+
&::-moz-range-track {
|
|
153
|
+
height: 0.75rem;
|
|
154
|
+
border-radius: 1rem
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/* Hue slider - static rainbow gradient */
|
|
159
|
+
& input[type=range].hue {
|
|
160
|
+
&::-webkit-slider-runnable-track {
|
|
161
|
+
background: linear-gradient(to right,
|
|
162
|
+
hsl(0 100% 50%), hsl(60 100% 50%), hsl(120 100% 50%),
|
|
163
|
+
hsl(180 100% 50%), hsl(240 100% 50%), hsl(300 100% 50%),
|
|
164
|
+
hsl(360 100% 50%))
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
&::-moz-range-track {
|
|
168
|
+
background: linear-gradient(to right,
|
|
169
|
+
hsl(0 100% 50%), hsl(60 100% 50%), hsl(120 100% 50%),
|
|
170
|
+
hsl(180 100% 50%), hsl(240 100% 50%), hsl(300 100% 50%),
|
|
171
|
+
hsl(360 100% 50%))
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* Alpha slider - checkerboard + color gradient (set via JS --cp-color) */
|
|
176
|
+
& input[type=range].alpha {
|
|
177
|
+
--cp-color: rgb(0, 0, 0);
|
|
178
|
+
position: relative;
|
|
179
|
+
|
|
180
|
+
&::-webkit-slider-runnable-track {
|
|
181
|
+
background:
|
|
182
|
+
linear-gradient(to right, transparent, var(--cp-color)),
|
|
183
|
+
repeating-conic-gradient(#e0e0e0 0% 25%, #ffffff 0% 50%) 0 0 / 0.5rem 0.5rem
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
&::-moz-range-track {
|
|
187
|
+
background:
|
|
188
|
+
linear-gradient(to right, transparent, var(--cp-color)),
|
|
189
|
+
repeating-conic-gradient(#e0e0e0 0% 25%, #ffffff 0% 50%) 0 0 / 0.5rem 0.5rem
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* Color space selector */
|
|
194
|
+
& select.color-format {
|
|
195
|
+
width: 8ch;
|
|
196
|
+
padding-inline-start: 0;
|
|
197
|
+
padding-inline-end: 0;
|
|
198
|
+
font-size: 0.6875rem;
|
|
199
|
+
|
|
200
|
+
&::picker-icon {
|
|
201
|
+
display: none
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/* Color value input */
|
|
206
|
+
& input.color-value {
|
|
207
|
+
flex: 1;
|
|
208
|
+
padding-inline-end: 0;
|
|
209
|
+
font-size: 50%
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* Alpha value input — hide spin buttons */
|
|
213
|
+
& input.alpha-value {
|
|
214
|
+
width: 6ch;
|
|
215
|
+
padding-inline-end: 0;
|
|
216
|
+
font-size: 50%;
|
|
217
|
+
-moz-appearance: textfield;
|
|
218
|
+
|
|
219
|
+
&::-webkit-inner-spin-button,
|
|
220
|
+
&::-webkit-outer-spin-button {
|
|
221
|
+
-webkit-appearance: none;
|
|
222
|
+
margin: 0
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/* Gradient layers container */
|
|
227
|
+
& .gradient-layers {
|
|
228
|
+
display: flex;
|
|
229
|
+
flex-direction: column;
|
|
230
|
+
gap: 0.5rem;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/* Floating menus (non-popover, manually positioned) */
|
|
234
|
+
& .layer-angle-menu,
|
|
235
|
+
& .stop-floating-menu {
|
|
236
|
+
display: none;
|
|
237
|
+
flex-direction: column;
|
|
238
|
+
min-width: 140px;
|
|
239
|
+
margin: 0;
|
|
240
|
+
padding: 0.25rem;
|
|
241
|
+
list-style: none;
|
|
242
|
+
background: var(--color-popover-surface, oklch(100% 0 0));
|
|
243
|
+
border-radius: var(--radius, 0.5rem);
|
|
244
|
+
box-shadow: rgba(15, 15, 15, 0.05) 0px 0px 0px 1px, rgba(15, 15, 15, 0.1) 0px 3px 6px, rgba(15, 15, 15, 0.2) 0px 9px 24px;
|
|
245
|
+
z-index: 100;
|
|
246
|
+
|
|
247
|
+
& :where(li) {
|
|
248
|
+
display: flex;
|
|
249
|
+
align-items: center;
|
|
250
|
+
padding: 0.25rem 0.5rem;
|
|
251
|
+
font-size: 0.8125rem;
|
|
252
|
+
color: var(--color-content-stark, oklch(16.6% 0.026 267));
|
|
253
|
+
border-radius: 6px;
|
|
254
|
+
cursor: pointer;
|
|
255
|
+
user-select: none;
|
|
256
|
+
|
|
257
|
+
&:hover {
|
|
258
|
+
background-color: var(--color-field-surface, oklch(91.79% 0.0029 264.26));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
&.negative:hover {
|
|
262
|
+
color: var(--color-negative-content, #ef4444);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
& :where(hr) {
|
|
267
|
+
margin: 0.25rem 0;
|
|
268
|
+
border: none;
|
|
269
|
+
border-top: 1px solid var(--color-line, color-mix(in oklch, oklch(16.6% 0.026 267) 11%, transparent));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
& :where(small) {
|
|
273
|
+
display: block;
|
|
274
|
+
padding: 0.25rem 0.5rem 0.125rem;
|
|
275
|
+
font-size: 0.6875rem;
|
|
276
|
+
color: var(--color-content-subtle, oklch(52.38% 0.017 264.26));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
& .layer-angle-menu.show,
|
|
281
|
+
& .stop-floating-menu {
|
|
282
|
+
display: flex;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* Stop-floating-menu always shows when present (added to DOM) */
|
|
286
|
+
& .stop-floating-menu {
|
|
287
|
+
display: flex;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/* Angle input label */
|
|
291
|
+
& .layer-angle {
|
|
292
|
+
display: inline-flex;
|
|
293
|
+
align-items: center;
|
|
294
|
+
cursor: ew-resize;
|
|
295
|
+
|
|
296
|
+
& input[type=number] {
|
|
297
|
+
width: 4ch;
|
|
298
|
+
padding: 0 0.125rem;
|
|
299
|
+
font-size: 0.625rem;
|
|
300
|
+
font-family: ui-monospace, monospace;
|
|
301
|
+
text-align: right;
|
|
302
|
+
background: transparent;
|
|
303
|
+
border: none;
|
|
304
|
+
color: var(--color-content-stark, oklch(16.6% 0.026 267));
|
|
305
|
+
outline: none;
|
|
306
|
+
cursor: ew-resize;
|
|
307
|
+
-moz-appearance: textfield !important;
|
|
308
|
+
appearance: textfield !important;
|
|
309
|
+
|
|
310
|
+
&:focus {
|
|
311
|
+
cursor: text;
|
|
312
|
+
background-color: var(--color-field-surface, oklch(91.79% 0.0029 264.26));
|
|
313
|
+
border-radius: 0.125rem;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
& span {
|
|
318
|
+
font-size: 0.625rem;
|
|
319
|
+
color: var(--color-content-subtle, oklch(52.38% 0.017 264.26));
|
|
320
|
+
pointer-events: none;
|
|
321
|
+
user-select: none;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/* Gradient stop bar */
|
|
326
|
+
& .gradient-layer {
|
|
327
|
+
position: relative;
|
|
328
|
+
width: 100%;
|
|
329
|
+
height: 0.75rem;
|
|
330
|
+
border-radius: 1rem;
|
|
331
|
+
cursor: pointer;
|
|
332
|
+
background: var(--color-field-surface, oklch(91.79% 0.0029 264.26));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
& .stop-handle {
|
|
336
|
+
position: absolute;
|
|
337
|
+
top: 50%;
|
|
338
|
+
width: 1rem;
|
|
339
|
+
height: 1rem;
|
|
340
|
+
border-radius: 50%;
|
|
341
|
+
border: 2px solid #ffffff;
|
|
342
|
+
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15), 0 1px 3px rgba(0, 0, 0, 0.25);
|
|
343
|
+
transform: translate(-50%, -50%);
|
|
344
|
+
cursor: grab;
|
|
345
|
+
z-index: 1;
|
|
346
|
+
touch-action: none;
|
|
347
|
+
|
|
348
|
+
&.active {
|
|
349
|
+
box-shadow: 0 0 0 2px var(--color-brand-content, #de6618), 0 1px 3px rgba(0, 0, 0, 0.25);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
&:active {
|
|
353
|
+
cursor: grabbing;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/* Stop context menu */
|
|
358
|
+
& .stop-context-menu {
|
|
359
|
+
min-width: 140px;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/* Eyedropper button */
|
|
363
|
+
& .eyedropper {}
|
|
364
|
+
|
|
365
|
+
/* Swatches grid */
|
|
366
|
+
& .swatches {
|
|
367
|
+
display: flex;
|
|
368
|
+
flex-wrap: wrap;
|
|
369
|
+
gap: 0.25rem;
|
|
370
|
+
|
|
371
|
+
& [data-color] {
|
|
372
|
+
width: 1.25rem;
|
|
373
|
+
height: 1.25rem;
|
|
374
|
+
border-radius: calc(var(--radius, 0.5rem) - 0.125rem);
|
|
375
|
+
border: 1px solid var(--color-line, color-mix(in oklch, oklch(16.6% 0.026 267) 11%, transparent));
|
|
376
|
+
cursor: pointer;
|
|
377
|
+
transition: var(--transition, all .05s ease-in-out);
|
|
378
|
+
|
|
379
|
+
&:hover {
|
|
380
|
+
scale: 1.15;
|
|
381
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2)
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/* Hide number input spin buttons inside colorpicker (high specificity, flat selectors) */
|
|
388
|
+
menu[popover].colorpicker .layer-angle input[type=number]::-webkit-inner-spin-button,
|
|
389
|
+
menu[popover].colorpicker .layer-angle input[type=number]::-webkit-outer-spin-button {
|
|
390
|
+
-webkit-appearance: none !important;
|
|
391
|
+
appearance: none !important;
|
|
392
|
+
margin: 0 !important;
|
|
393
|
+
display: none !important;
|
|
394
|
+
width: 0 !important;
|
|
395
|
+
height: 0 !important;
|
|
396
|
+
}
|
|
397
|
+
}
|
package/lib/manifest.js
CHANGED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/* Manifest SVG */
|
|
2
|
+
|
|
3
|
+
const svgCache = new Map();
|
|
4
|
+
|
|
5
|
+
function resolveFetchPath(pathOrContent) {
|
|
6
|
+
let resolved = pathOrContent;
|
|
7
|
+
if (!pathOrContent.startsWith('/')) {
|
|
8
|
+
const base = (typeof window.getManifestBase === 'function' ? window.getManifestBase() : '') || '';
|
|
9
|
+
const basePath = base.replace(/\/$/, '') || '';
|
|
10
|
+
resolved = (basePath ? basePath + '/' : '/') + pathOrContent;
|
|
11
|
+
}
|
|
12
|
+
return resolved;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function looksLikeInlineSvgMarkup(str) {
|
|
16
|
+
if (typeof str !== 'string') return false;
|
|
17
|
+
const t = str.trim();
|
|
18
|
+
return t.length > 0 && t.startsWith('<') && /<svg[\s>]/i.test(t);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isLikelySvgFilePath(str) {
|
|
22
|
+
if (typeof str !== 'string') return false;
|
|
23
|
+
const t = str.trim();
|
|
24
|
+
if (!t || looksLikeInlineSvgMarkup(t)) return false;
|
|
25
|
+
return (
|
|
26
|
+
t.includes('.svg') ||
|
|
27
|
+
t.startsWith('/') ||
|
|
28
|
+
(t.includes('/') && !t.includes('<'))
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseSvgRoot(svgText) {
|
|
33
|
+
const trimmed = svgText.trim();
|
|
34
|
+
const parser = new DOMParser();
|
|
35
|
+
const doc = parser.parseFromString(trimmed, 'image/svg+xml');
|
|
36
|
+
const err = doc.querySelector('parsererror');
|
|
37
|
+
if (err) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const root = doc.documentElement;
|
|
41
|
+
if (!root || root.tagName.toLowerCase() !== 'svg') {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
return root;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function resolveSvgString(pathOrContent) {
|
|
48
|
+
if (pathOrContent === undefined || pathOrContent === null) {
|
|
49
|
+
return { ok: false, error: 'empty', text: '' };
|
|
50
|
+
}
|
|
51
|
+
const str = typeof pathOrContent === 'string' ? pathOrContent : String(pathOrContent);
|
|
52
|
+
if (!str.trim()) {
|
|
53
|
+
return { ok: false, error: 'empty', text: '' };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (looksLikeInlineSvgMarkup(str)) {
|
|
57
|
+
return { ok: true, text: str };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!isLikelySvgFilePath(str)) {
|
|
61
|
+
if (parseSvgRoot(str)) {
|
|
62
|
+
return { ok: true, text: str };
|
|
63
|
+
}
|
|
64
|
+
return { ok: false, error: 'not-path', text: str };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let resolvedPath;
|
|
68
|
+
try {
|
|
69
|
+
resolvedPath = resolveFetchPath(str);
|
|
70
|
+
|
|
71
|
+
if (svgCache.has(resolvedPath)) {
|
|
72
|
+
return { ok: true, text: svgCache.get(resolvedPath) };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const response = await fetch(resolvedPath);
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
console.warn(`[Manifest SVG] Failed to fetch: ${resolvedPath}`);
|
|
78
|
+
const errText = `<!-- SVG load error: ${resolvedPath} -->`;
|
|
79
|
+
svgCache.set(resolvedPath, errText);
|
|
80
|
+
return { ok: false, error: 'fetch', text: errText };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const text = await response.text();
|
|
84
|
+
svgCache.set(resolvedPath, text);
|
|
85
|
+
return { ok: true, text };
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error(`[Manifest SVG] Error fetching: ${str}`, error);
|
|
88
|
+
const errText = `<!-- SVG fetch error: ${error.message} -->`;
|
|
89
|
+
if (resolvedPath) {
|
|
90
|
+
svgCache.set(resolvedPath, errText);
|
|
91
|
+
}
|
|
92
|
+
return { ok: false, error: 'fetch', text: errText };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function injectSvgChildren(hostEl, svgText) {
|
|
97
|
+
const root = parseSvgRoot(svgText);
|
|
98
|
+
if (!root) {
|
|
99
|
+
console.warn('[Manifest SVG] Invalid SVG markup');
|
|
100
|
+
hostEl.replaceChildren();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const clone = document.importNode(root, true);
|
|
104
|
+
hostEl.replaceChildren(clone);
|
|
105
|
+
|
|
106
|
+
if (window.Alpine && typeof window.Alpine.initTree === 'function') {
|
|
107
|
+
if (window.Alpine.nextTick) {
|
|
108
|
+
window.Alpine.nextTick(() => {
|
|
109
|
+
window.Alpine.initTree(hostEl);
|
|
110
|
+
});
|
|
111
|
+
} else {
|
|
112
|
+
setTimeout(() => {
|
|
113
|
+
window.Alpine.initTree(hostEl);
|
|
114
|
+
}, 0);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function initializeSvgPlugin() {
|
|
120
|
+
try {
|
|
121
|
+
const isPrerenderedPage = !!(
|
|
122
|
+
document.querySelector('meta[name="manifest:prerendered"]') &&
|
|
123
|
+
document.querySelector('meta[name="manifest:prerendered"]').getAttribute('content') !== '0'
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
Alpine.directive('svg', (el, { expression }, { effect, evaluateLater }) => {
|
|
127
|
+
if (!expression) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const hasBakedContent =
|
|
132
|
+
isPrerenderedPage &&
|
|
133
|
+
el.querySelector('svg') &&
|
|
134
|
+
el.querySelector('svg').parentElement === el;
|
|
135
|
+
|
|
136
|
+
if (hasBakedContent) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Only wrap bare tokens that look like file paths — not Alpine identifiers (e.g. `star`
|
|
141
|
+
// from x-data). Same intent as x-markdown path literals vs expressions.
|
|
142
|
+
let processedExpression = expression;
|
|
143
|
+
const looksLikeUnquotedPath =
|
|
144
|
+
!expression.includes('+') &&
|
|
145
|
+
!expression.includes('`') &&
|
|
146
|
+
!expression.includes('${') &&
|
|
147
|
+
!expression.startsWith('$') &&
|
|
148
|
+
!expression.startsWith("'") &&
|
|
149
|
+
!expression.startsWith('"') &&
|
|
150
|
+
(expression.includes('/') || expression.includes('.svg'));
|
|
151
|
+
if (looksLikeUnquotedPath) {
|
|
152
|
+
processedExpression = `'${expression.replace(/'/g, "\\'")}'`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const getSvgSource = evaluateLater(processedExpression);
|
|
156
|
+
let lastText = null;
|
|
157
|
+
|
|
158
|
+
effect(() => {
|
|
159
|
+
getSvgSource(async (pathOrContent) => {
|
|
160
|
+
if (pathOrContent === undefined || pathOrContent === '' || pathOrContent === null) {
|
|
161
|
+
el.replaceChildren();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const resolved = await resolveSvgString(
|
|
166
|
+
pathOrContent === undefined ? expression : pathOrContent
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
if (!resolved.ok && resolved.error === 'not-path') {
|
|
170
|
+
console.warn('[Manifest SVG] Expected a file path or SVG markup');
|
|
171
|
+
el.replaceChildren();
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const text = resolved.text || '';
|
|
176
|
+
if (text === lastText) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
lastText = text;
|
|
180
|
+
|
|
181
|
+
if (!text.trim()) {
|
|
182
|
+
el.replaceChildren();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
injectSvgChildren(el, text);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.error('[Manifest] Failed to initialize SVG plugin:', error);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let svgPluginInitialized = false;
|
|
196
|
+
|
|
197
|
+
async function ensureSvgPluginInitialized() {
|
|
198
|
+
if (svgPluginInitialized) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (!window.Alpine || typeof window.Alpine.directive !== 'function') {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
svgPluginInitialized = true;
|
|
206
|
+
await initializeSvgPlugin();
|
|
207
|
+
|
|
208
|
+
if (window.Alpine && typeof window.Alpine.initTree === 'function') {
|
|
209
|
+
const existing = document.querySelectorAll('[x-svg]');
|
|
210
|
+
existing.forEach((el) => {
|
|
211
|
+
if (!el.__x) {
|
|
212
|
+
window.Alpine.initTree(el);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
window.ensureSvgPluginInitialized = ensureSvgPluginInitialized;
|
|
219
|
+
|
|
220
|
+
if (document.readyState === 'loading') {
|
|
221
|
+
document.addEventListener('DOMContentLoaded', ensureSvgPluginInitialized);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
document.addEventListener('alpine:init', ensureSvgPluginInitialized);
|
|
225
|
+
|
|
226
|
+
if (window.Alpine && typeof window.Alpine.directive === 'function') {
|
|
227
|
+
ensureSvgPluginInitialized();
|
|
228
|
+
}
|