mnfst 0.5.80 → 0.5.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/LICENSE +1 -1
  2. package/lib/manifest.accordion.css +4 -4
  3. package/lib/manifest.appwrite.auth.js +66 -33
  4. package/lib/manifest.avatar.css +8 -8
  5. package/lib/manifest.button.css +7 -7
  6. package/lib/manifest.checkbox.css +5 -5
  7. package/lib/manifest.code.css +152 -193
  8. package/lib/manifest.code.js +841 -881
  9. package/lib/manifest.code.min.css +1 -1
  10. package/lib/manifest.colorpicker.css +11 -11
  11. package/lib/manifest.components.js +25 -155
  12. package/lib/manifest.css +278 -230
  13. package/lib/manifest.data.js +46 -2
  14. package/lib/manifest.dialog.css +2 -2
  15. package/lib/manifest.divider.css +2 -2
  16. package/lib/manifest.dropdown.css +9 -9
  17. package/lib/manifest.form.css +10 -10
  18. package/lib/manifest.input.css +9 -9
  19. package/lib/manifest.integrity.json +26 -0
  20. package/lib/manifest.js +60 -5
  21. package/lib/manifest.markdown.js +192 -79
  22. package/lib/manifest.min.css +1 -1
  23. package/lib/manifest.radio.css +1 -1
  24. package/lib/manifest.range.css +7 -7
  25. package/lib/manifest.resize.css +1 -1
  26. package/lib/manifest.router.js +49 -76
  27. package/lib/manifest.schema.json +1 -1
  28. package/lib/manifest.sidebar.css +5 -6
  29. package/lib/manifest.slides.css +5 -5
  30. package/lib/manifest.svg.js +75 -5
  31. package/lib/manifest.switch.css +4 -4
  32. package/lib/manifest.table.css +4 -4
  33. package/lib/manifest.theme.css +46 -41
  34. package/lib/manifest.toast.css +7 -7
  35. package/lib/manifest.tooltip.css +3 -3
  36. package/lib/manifest.tooltips.js +41 -0
  37. package/lib/manifest.typography.css +124 -69
  38. package/lib/manifest.utilities.css +48 -54
  39. package/lib/manifest.utilities.js +9 -29
  40. package/package.json +4 -7
  41. package/lib/manifest.export.js +0 -535
  42. package/lib/manifest.virtual.js +0 -319
@@ -2,6 +2,64 @@
2
2
 
3
3
  const svgCache = new Map();
4
4
 
5
+ // Shared DOMPurify loader (only fetched when the .safe modifier is used).
6
+ // SVG natively supports <script>, on* event handlers, javascript: URLs in
7
+ // href, and <foreignObject> with HTML inside — any of which become an XSS
8
+ // vector when the SVG source is user-supplied. The .safe modifier opts into
9
+ // DOMPurify with the SVG profile so authors rendering user-uploaded SVGs
10
+ // (e.g. profile avatars from an Appwrite bucket) don't have to write
11
+ // per-source sanitization.
12
+ //
13
+ // Defined on window so manifest.markdown.js can share the same loader and
14
+ // in-flight promise — otherwise both plugins' top-level `let purifyPromise`
15
+ // declarations collide in the realm's global lexical env and the second
16
+ // script throws SyntaxError ("Identifier 'purifyPromise' has already been
17
+ // declared") at parse time, taking the whole plugin offline.
18
+ if (!window.ManifestDOMPurify) {
19
+ window.ManifestDOMPurify = {
20
+ _promise: null,
21
+ load() {
22
+ if (typeof window.DOMPurify !== 'undefined') return Promise.resolve(window.DOMPurify);
23
+ if (this._promise) return this._promise;
24
+ this._promise = new Promise((resolve, reject) => {
25
+ const script = document.createElement('script');
26
+ script.src = 'https://cdn.jsdelivr.net/npm/dompurify@latest/dist/purify.min.js';
27
+ script.onload = () => {
28
+ if (typeof window.DOMPurify !== 'undefined') {
29
+ resolve(window.DOMPurify);
30
+ } else {
31
+ this._promise = null;
32
+ reject(new Error('DOMPurify failed to load'));
33
+ }
34
+ };
35
+ script.onerror = (err) => {
36
+ this._promise = null;
37
+ reject(err);
38
+ };
39
+ document.head.appendChild(script);
40
+ });
41
+ return this._promise;
42
+ }
43
+ };
44
+ }
45
+
46
+ // Return a sanitized SVG string when the .safe modifier is on; pass through
47
+ // otherwise. Uses DOMPurify's SVG profile which strips <script>, on* event
48
+ // handlers, javascript: URLs, and dangerous foreignObject HTML — while
49
+ // keeping the visual SVG markup intact.
50
+ async function maybeSanitizeSvg(svgText, safe) {
51
+ if (!safe) return svgText;
52
+ try {
53
+ const DOMPurify = await window.ManifestDOMPurify.load();
54
+ return DOMPurify.sanitize(svgText, {
55
+ USE_PROFILES: { svg: true, svgFilters: true }
56
+ });
57
+ } catch {
58
+ console.warn('[Manifest SVG] x-svg.safe: DOMPurify unavailable — skipping injection.');
59
+ return '';
60
+ }
61
+ }
62
+
5
63
  function resolveFetchPath(pathOrContent) {
6
64
  let resolved = pathOrContent;
7
65
  if (!pathOrContent.startsWith('/')) {
@@ -123,11 +181,17 @@ async function initializeSvgPlugin() {
123
181
  document.querySelector('meta[name="manifest:prerendered"]').getAttribute('content') !== '0'
124
182
  );
125
183
 
126
- Alpine.directive('svg', (el, { expression }, { effect, evaluateLater }) => {
184
+ Alpine.directive('svg', (el, { expression, modifiers }, { effect, evaluateLater }) => {
127
185
  if (!expression) {
128
186
  return;
129
187
  }
130
188
 
189
+ // Opt-in DOMPurify sanitization for user-supplied SVG. Default is
190
+ // unsanitized — Manifest keeps full SVG fidelity (filters, scripts
191
+ // used for animations, etc.). Use .safe when the SVG source can
192
+ // come from untrusted parties (uploaded avatars, third-party APIs).
193
+ const safe = Array.isArray(modifiers) && modifiers.includes('safe');
194
+
131
195
  const hasBakedContent =
132
196
  isPrerenderedPage &&
133
197
  el.querySelector('svg') &&
@@ -172,13 +236,19 @@ async function initializeSvgPlugin() {
172
236
  return;
173
237
  }
174
238
 
175
- const text = resolved.text || '';
176
- if (text === lastText) {
239
+ const rawText = resolved.text || '';
240
+ if (rawText === lastText) {
241
+ return;
242
+ }
243
+ lastText = rawText;
244
+
245
+ if (!rawText.trim()) {
246
+ el.replaceChildren();
177
247
  return;
178
248
  }
179
- lastText = text;
180
249
 
181
- if (!text.trim()) {
250
+ const text = await maybeSanitizeSvg(rawText, safe);
251
+ if (!text || !text.trim()) {
182
252
  el.replaceChildren();
183
253
  return;
184
254
  }
@@ -21,23 +21,23 @@
21
21
  width: calc(var(--spacing-field-height, 2.25rem) * 0.65 - 0.125rem * 2);
22
22
  height: calc(var(--spacing-field-height, 2.25rem) * 0.65 - 0.125rem * 2);
23
23
  border-radius: 50%;
24
- background-color: color-mix(in oklch, var(--color-field-surface, oklch(91.79% 0.0029 264.26)) 60%, var(--color-field-inverse, oklch(16.6% 0.026 267)));
24
+ background-color: color-mix(in oklch, var(--color-field-surface, color-mix(darkslategray 10%, transparent)) 60%, var(--color-field-inverse, darkslategray));
25
25
  box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
26
26
  transition: var(--transition, all .05s ease-in-out)
27
27
  }
28
28
 
29
29
  /* Checked */
30
30
  &:checked {
31
- background-color: var(--color-field-surface, oklch(91.79% 0.0029 264.26));
31
+ background-color: var(--color-field-surface, color-mix(darkslategray 10%, transparent));
32
32
 
33
33
  /* Marker */
34
34
  &::before {
35
35
  left: calc(100% - (var(--spacing-field-height, 2.25rem) * 0.65) + 0.125rem);
36
- background-color: var(--color-field-inverse, oklch(16.6% 0.026 267))
36
+ background-color: var(--color-field-inverse, darkslategray)
37
37
  }
38
38
 
39
39
  &:hover {
40
- background-color: var(--color-field-surface-hover, oklch(89.24% 0.0024 12.48))
40
+ background-color: var(--color-field-surface-hover, color-mix(darkslategray 15%, transparent))
41
41
  }
42
42
  }
43
43
  }
@@ -17,8 +17,8 @@
17
17
 
18
18
  /* Header row */
19
19
  :where(thead, .grid-header > *) {
20
- background-color: var(--color-surface-1, oklch(98.17% 0.0005 95.87));
21
- border-bottom: 1px solid var(--color-line, oklch(48.3% 0.006422 17.4 / 0.15))
20
+ background-color: var(--color-surface-1, whitesmoke);
21
+ border-bottom: 1px solid var(--color-line, color-mix(darkslategray 10%, transparent))
22
22
  }
23
23
 
24
24
  /* Header cell */
@@ -28,7 +28,7 @@
28
28
 
29
29
  /* Row */
30
30
  :where(tr, .grid-row > *) {
31
- border-bottom: 1px solid var(--color-line, oklch(48.3% 0.006422 17.4 / 0.15))
31
+ border-bottom: 1px solid var(--color-line, color-mix(darkslategray 10%, transparent))
32
32
  }
33
33
 
34
34
  /* Cell */
@@ -59,7 +59,7 @@
59
59
  /* Striped utility class */
60
60
  &.striped {
61
61
  :where(tr:nth-child(even), .grid-row:nth-child(even)) {
62
- background-color: var(--color-surface-1, oklch(98.17% 0.0005 95.87))
62
+ background-color: var(--color-surface-1, whitesmoke)
63
63
  }
64
64
 
65
65
  :where(tr:nth-child(odd), .grid-row:nth-child(odd)) {
@@ -8,19 +8,19 @@
8
8
  ::selection {
9
9
 
10
10
  /* Default palette */
11
- --color-50: oklch(100% 0 0);
12
- --color-100: oklch(98.17% 0.0005 95.87);
13
- --color-200: oklch(96.27% 0.0026 252.34);
14
- --color-300: oklch(91.79% 0.0029 264.26);
15
- --color-400: oklch(89.24% 0.0024 12.48);
16
- --color-500: oklch(67.4% 0.0318 251.27);
17
- --color-600: oklch(48.26% 0.0365 255.09);
18
- --color-700: oklch(28.7% 0.030787 270.1);
19
- --color-800: oklch(20.7% 0.026326 268.7);
20
- --color-900: oklch(16.6% 0.026 267);
21
- --color-950: oklch(3.89% 0.0181 262.25);
11
+ --color-50: white;
12
+ --color-100: whitesmoke;
13
+ --color-200: gainsboro;
14
+ --color-300: lightgray;
15
+ --color-400: silver;
16
+ --color-500: darkgray;
17
+ --color-600: gray;
18
+ --color-700: slategray;
19
+ --color-800: dimgray;
20
+ --color-900: darkslategray;
21
+ --color-950: black;
22
22
 
23
- /* Light theme */
23
+ /* Light mode */
24
24
  --color-page: var(--color-50);
25
25
  --color-surface-1: var(--color-100);
26
26
  --color-surface-2: var(--color-200);
@@ -28,35 +28,35 @@
28
28
  --color-content-stark: var(--color-900);
29
29
  --color-content-neutral: var(--color-600);
30
30
  --color-content-subtle: var(--color-500);
31
- --color-field-surface: var(--color-300);
32
- --color-field-surface-hover: var(--color-400);
31
+ --color-field-surface: color-mix(var(--color-content-stark) 10%, transparent);
32
+ --color-field-surface-hover: color-mix(var(--color-content-stark) 15%, transparent);
33
33
  --color-field-inverse: var(--color-content-stark);
34
34
  --color-popover-surface: var(--color-page);
35
- --color-line: color-mix(in oklch, var(--color-content-stark) 11%, transparent);
36
- --color-brand-surface: #f6c07b;
37
- --color-brand-surface-hover: #f19b46;
38
- --color-brand-inverse: #763518;
39
- --color-brand-content: #de6618;
40
- --color-accent-surface: #ffccd3;
41
- --color-accent-surface-hover: #ffa1ad;
42
- --color-accent-inverse: #a50036;
43
- --color-accent-content: #ff637e;
44
- --color-positive-surface: #16a34a;
45
- --color-positive-surface-hover: #166534;
46
- --color-positive-inverse: white;
47
- --color-positive-content: var(--color-positive-surface);
48
- --color-negative-surface: #ef4444;
49
- --color-negative-surface-hover: #dc2626;
50
- --color-negative-inverse: white;
51
- --color-negative-content: var(--color-negative-surface);
35
+ --color-line: color-mix(var(--color-content-stark) 10%, transparent);
36
+ --color-brand-surface: goldenrod;
37
+ --color-brand-surface-hover: darkgoldenrod;
38
+ --color-brand-inverse: white;
39
+ --color-brand-content: goldenrod;
40
+ --color-accent-surface: var(--color-content-stark);
41
+ --color-accent-surface-hover: color-mix(var(--color-content-stark) 90%, var(--color-page));
42
+ --color-accent-inverse: var(--color-page);
43
+ --color-accent-content: var(--color-content-stark);
44
+ --color-positive-surface: palegreen;
45
+ --color-positive-surface-hover: lightgreen;
46
+ --color-positive-inverse: darkgreen;
47
+ --color-positive-content: limegreen;
48
+ --color-negative-surface: salmon;
49
+ --color-negative-surface-hover: lightcoral;
50
+ --color-negative-inverse: maroon;
51
+ --color-negative-content: crimson;
52
52
 
53
53
  /* Fonts */
54
- --font-sans: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
54
+ --font-sans: Inter, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
55
55
 
56
56
  /* Sizes */
57
57
  --radius: 0.5rem;
58
58
  --spacing: 0.25rem;
59
- --spacing-content-width: 68.75rem;
59
+ --spacing-content-width: 74rem;
60
60
  --spacing-field-padding: calc(var(--spacing) * 2.5);
61
61
  --spacing-field-height: calc(var(--spacing) * 9);
62
62
  --spacing-popover-offset: calc(var(--spacing) * 2);
@@ -75,7 +75,7 @@
75
75
  --icon-toast-dismiss: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M18 6L6 18M6 6l12 12'/%3E%3C/svg%3E");
76
76
  }
77
77
 
78
- /* Dark theme overrides */
78
+ /* Dark mode overrides */
79
79
  .dark {
80
80
  --color-page: var(--color-950);
81
81
  --color-surface-1: var(--color-900);
@@ -83,20 +83,25 @@
83
83
  --color-surface-3: var(--color-700);
84
84
  --color-field-surface: var(--color-700);
85
85
  --color-field-surface-hover: var(--color-600);
86
- --color-popover-surface: var(--color-700);
86
+ --color-popover-surface: var(--color-900);
87
87
  --color-content-stark: var(--color-50);
88
88
  --color-content-neutral: var(--color-400);
89
89
  --color-content-subtle: var(--color-500);
90
- --color-brand-content: #f6c07b;
91
- --color-accent-content: #ffa1ad;
92
90
 
93
91
  /* Popover form elements */
94
92
  :where([popover]) {
95
- --color-field-surface: color-mix(in oklch, var(--color-600) 60%, var(--color-700));
96
- --color-field-surface-hover: color-mix(in oklch, var(--color-600) 80%, var(--color-700));
93
+ --color-field-surface: color-mix(var(--color-600) 60%, var(--color-700));
94
+ --color-field-surface-hover: color-mix(var(--color-600) 80%, var(--color-700))
97
95
  }
98
96
  }
99
97
 
98
+ @font-face {
99
+ font-family: 'Inter';
100
+ src: url('/assets/fonts/Inter.woff2') format('woff2');
101
+ font-weight: 400 500 600 700;
102
+ font-display: swap
103
+ }
104
+
100
105
  @layer base {
101
106
 
102
107
  /* Default font and colors */
@@ -110,12 +115,12 @@
110
115
 
111
116
  /* Text selection */
112
117
  ::selection {
113
- background-color: color-mix(in oklch, var(--color-content-stark) 90%, transparent);
118
+ background-color: color-mix(currentColor 25%, transparent)
114
119
  }
115
120
 
116
121
  /* Focus state */
117
122
  :where(:focus-visible, label:has(input[type=search], input[type=file]):focus-within) {
118
123
  outline: none;
119
- box-shadow: 0 0 0 2px color-mix(in oklch, var(--color-content-stark) 35%, transparent);
124
+ box-shadow: 0 0 0 2px color-mix(var(--color-content-stark) 30%, transparent)
120
125
  }
121
126
  }
@@ -17,8 +17,8 @@
17
17
  :where(.toast) {
18
18
  display: flex;
19
19
  max-width: 90vw;
20
- background-color: var(--color-popover-surface, oklch(100% 0 0));
21
- border: 1px solid var(--color-line, oklch(48.3% 0.006422 17.4 / 0.15));
20
+ background-color: var(--color-popover-surface, white);
21
+ border: 1px solid var(--color-line, color-mix(darkslategray 10%, transparent));
22
22
  border-radius: calc(var(--radius, 0.5rem) * 2);
23
23
  box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
24
24
  overflow: hidden;
@@ -47,7 +47,7 @@
47
47
  /* Dismiss button */
48
48
  :where(.toast-dismiss-button) {
49
49
  position: relative;
50
- border-inline-start: 1px solid color-mix(in oklch, var(--color-content-stark, oklch(16.6% 0.026 267)) 20%, transparent);
50
+ border-inline-start: 1px solid color-mix(in oklch, var(--color-content-stark, darkslategray) 20%, transparent);
51
51
  border-radius: 0;
52
52
 
53
53
  /* Icon */
@@ -58,7 +58,7 @@
58
58
  left: 50%;
59
59
  width: 50%;
60
60
  height: 50%;
61
- background-color: var(--color-field-inverse, oklch(16.6% 0.026 267));
61
+ background-color: var(--color-field-inverse, darkslategray);
62
62
  mask-image: var(--icon-toast-dismiss, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M18 6L6 18M6 6l12 12'/%3E%3C/svg%3E"));
63
63
  mask-repeat: no-repeat;
64
64
  mask-size: 100% 100%;
@@ -70,11 +70,11 @@
70
70
 
71
71
  /* Brand, accent, positive, negative utility class overrides (see Utilities) */
72
72
  :where(.toast.brand, .toast.accent, .toast.positive, .toast.negative) {
73
- color: var(--color-field-inverse, oklch(16.6% 0.026 267));
74
- background-color: var(--color-field-surface, oklch(91.79% 0.0029 264.26));
73
+ color: var(--color-field-inverse, darkslategray);
74
+ background-color: var(--color-field-surface, color-mix(darkslategray 10%, transparent));
75
75
 
76
76
  :where(.toast-dismiss-button) {
77
- border-inline-start: 1px solid color-mix(in oklch, var(--color-field-inverse, oklch(16.6% 0.026 267)) 20%, transparent);
77
+ border-inline-start: 1px solid color-mix(in oklch, var(--color-field-inverse, darkslategray) 20%, transparent);
78
78
  }
79
79
  }
80
80
 
@@ -42,14 +42,14 @@
42
42
  margin: var(--spacing-popover-offset, 0.5rem) 0;
43
43
  padding: calc(var(--spacing, 0.25rem) * .5) calc(var(--spacing, 0.25rem) * 2);
44
44
  font-size: 0.875rem;
45
- color: var(--color-page, oklch(100% 0 0));
46
- background-color: var(--color-content-stark, oklch(16.6% 0.026 267));
45
+ color: var(--color-page, white);
46
+ background-color: var(--color-content-stark, darkslategray);
47
47
  border: 0 none;
48
48
  border-radius: var(--radius, 0.5rem);
49
49
  box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
50
50
 
51
51
  &:hover {
52
- transition-delay: var(--tooltip-hover-delay, 1s)
52
+ transition-delay: var(--tooltip-hover-delay, .5s)
53
53
  }
54
54
 
55
55
  /* Leading icon */
@@ -306,6 +306,47 @@ function initializeTooltipPlugin() {
306
306
  if (t.classList && t.classList.contains('tooltip') && t.getAttribute('popover') === 'hint') return;
307
307
  hideAnySingleton();
308
308
  }, true);
309
+
310
+ // ---- Public programmatic-show API ----
311
+ //
312
+ // Flash a tooltip in response to an action (e.g. the code plugin's inline
313
+ // copy confirmation) without requiring the trigger to carry an x-tooltip
314
+ // directive. The trigger element acts as the anchor; the singleton is
315
+ // reused, so this respects chain mode / focus behaviour just like a
316
+ // hover-shown tooltip would. Auto-hides after `durationMs`.
317
+ //
318
+ // `positions` accepts the same vocabulary as the x-tooltip directive's
319
+ // modifiers — array of any subset of ['top','bottom','start','end',
320
+ // 'center','corner']. Joined with '-' to form the position class
321
+ // (e.g. ['top','end'] → '.top-end'), matching what `x-tooltip.top.end`
322
+ // would emit.
323
+ window.ManifestTooltips = window.ManifestTooltips || {};
324
+ window.ManifestTooltips.showTransient = function (triggerEl, contentHtml, durationMs, positions) {
325
+ if (!triggerEl) return;
326
+ const duration = typeof durationMs === 'number' ? durationMs : 1500;
327
+ const validPositions = ['top', 'bottom', 'start', 'end', 'center', 'corner'];
328
+ let resolvedPositions = [];
329
+ if (Array.isArray(positions)) {
330
+ resolvedPositions = positions.filter(p => validPositions.includes(p));
331
+ } else if (typeof positions === 'string' && positions) {
332
+ resolvedPositions = positions.split(/[.\-\s]+/).filter(p => validPositions.includes(p));
333
+ }
334
+ cancelPendingShow();
335
+ cancelPendingHide();
336
+ showSingletonFor(triggerEl, contentHtml || '', resolvedPositions);
337
+ clearTimeout(triggerEl._tooltipTransientTimer);
338
+ triggerEl._tooltipTransientTimer = setTimeout(() => {
339
+ triggerEl._tooltipTransientTimer = null;
340
+ const host = getTooltipHostForTrigger(triggerEl);
341
+ const s = _singletons.get(host);
342
+ if (s && s.activeTrigger === triggerEl && s.el.matches(':popover-open')) {
343
+ try { s.el.hidePopover(); } catch { /* popover already closed */ }
344
+ s.activeTrigger = null;
345
+ markTooltipHidden();
346
+ scheduleAnchorRestore(triggerEl);
347
+ }
348
+ }, duration);
349
+ };
309
350
  }
310
351
 
311
352
  // ---- Plugin init boilerplate ----