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.
- package/LICENSE +1 -1
- package/lib/manifest.accordion.css +4 -4
- package/lib/manifest.appwrite.auth.js +66 -33
- package/lib/manifest.avatar.css +8 -8
- package/lib/manifest.button.css +7 -7
- package/lib/manifest.checkbox.css +5 -5
- package/lib/manifest.code.css +152 -193
- package/lib/manifest.code.js +841 -881
- package/lib/manifest.code.min.css +1 -1
- package/lib/manifest.colorpicker.css +11 -11
- package/lib/manifest.components.js +25 -155
- package/lib/manifest.css +278 -230
- package/lib/manifest.data.js +46 -2
- package/lib/manifest.dialog.css +2 -2
- package/lib/manifest.divider.css +2 -2
- package/lib/manifest.dropdown.css +9 -9
- package/lib/manifest.form.css +10 -10
- package/lib/manifest.input.css +9 -9
- package/lib/manifest.integrity.json +26 -0
- package/lib/manifest.js +60 -5
- package/lib/manifest.markdown.js +192 -79
- package/lib/manifest.min.css +1 -1
- package/lib/manifest.radio.css +1 -1
- package/lib/manifest.range.css +7 -7
- package/lib/manifest.resize.css +1 -1
- package/lib/manifest.router.js +49 -76
- package/lib/manifest.schema.json +1 -1
- package/lib/manifest.sidebar.css +5 -6
- package/lib/manifest.slides.css +5 -5
- package/lib/manifest.svg.js +75 -5
- package/lib/manifest.switch.css +4 -4
- package/lib/manifest.table.css +4 -4
- package/lib/manifest.theme.css +46 -41
- package/lib/manifest.toast.css +7 -7
- package/lib/manifest.tooltip.css +3 -3
- package/lib/manifest.tooltips.js +41 -0
- package/lib/manifest.typography.css +124 -69
- package/lib/manifest.utilities.css +48 -54
- package/lib/manifest.utilities.js +9 -29
- package/package.json +4 -7
- package/lib/manifest.export.js +0 -535
- package/lib/manifest.virtual.js +0 -319
package/lib/manifest.svg.js
CHANGED
|
@@ -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
|
|
176
|
-
if (
|
|
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
|
-
|
|
250
|
+
const text = await maybeSanitizeSvg(rawText, safe);
|
|
251
|
+
if (!text || !text.trim()) {
|
|
182
252
|
el.replaceChildren();
|
|
183
253
|
return;
|
|
184
254
|
}
|
package/lib/manifest.switch.css
CHANGED
|
@@ -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,
|
|
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,
|
|
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,
|
|
36
|
+
background-color: var(--color-field-inverse, darkslategray)
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
&:hover {
|
|
40
|
-
background-color: var(--color-field-surface-hover,
|
|
40
|
+
background-color: var(--color-field-surface-hover, color-mix(darkslategray 15%, transparent))
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
}
|
package/lib/manifest.table.css
CHANGED
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
/* Header row */
|
|
19
19
|
:where(thead, .grid-header > *) {
|
|
20
|
-
background-color: var(--color-surface-1,
|
|
21
|
-
border-bottom: 1px solid var(--color-line,
|
|
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,
|
|
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,
|
|
62
|
+
background-color: var(--color-surface-1, whitesmoke)
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
:where(tr:nth-child(odd), .grid-row:nth-child(odd)) {
|
package/lib/manifest.theme.css
CHANGED
|
@@ -8,19 +8,19 @@
|
|
|
8
8
|
::selection {
|
|
9
9
|
|
|
10
10
|
/* Default palette */
|
|
11
|
-
--color-50:
|
|
12
|
-
--color-100:
|
|
13
|
-
--color-200:
|
|
14
|
-
--color-300:
|
|
15
|
-
--color-400:
|
|
16
|
-
--color-500:
|
|
17
|
-
--color-600:
|
|
18
|
-
--color-700:
|
|
19
|
-
--color-800:
|
|
20
|
-
--color-900:
|
|
21
|
-
--color-950:
|
|
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
|
|
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-
|
|
32
|
-
--color-field-surface-hover: var(--color-
|
|
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(
|
|
36
|
-
--color-brand-surface:
|
|
37
|
-
--color-brand-surface-hover:
|
|
38
|
-
--color-brand-inverse:
|
|
39
|
-
--color-brand-content:
|
|
40
|
-
--color-accent-surface:
|
|
41
|
-
--color-accent-surface-hover:
|
|
42
|
-
--color-accent-inverse:
|
|
43
|
-
--color-accent-content:
|
|
44
|
-
--color-positive-surface:
|
|
45
|
-
--color-positive-surface-hover:
|
|
46
|
-
--color-positive-inverse:
|
|
47
|
-
--color-positive-content:
|
|
48
|
-
--color-negative-surface:
|
|
49
|
-
--color-negative-surface-hover:
|
|
50
|
-
--color-negative-inverse:
|
|
51
|
-
--color-negative-content:
|
|
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:
|
|
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
|
|
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-
|
|
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(
|
|
96
|
-
--color-field-surface-hover: color-mix(
|
|
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(
|
|
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(
|
|
124
|
+
box-shadow: 0 0 0 2px color-mix(var(--color-content-stark) 30%, transparent)
|
|
120
125
|
}
|
|
121
126
|
}
|
package/lib/manifest.toast.css
CHANGED
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
:where(.toast) {
|
|
18
18
|
display: flex;
|
|
19
19
|
max-width: 90vw;
|
|
20
|
-
background-color: var(--color-popover-surface,
|
|
21
|
-
border: 1px solid var(--color-line,
|
|
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,
|
|
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,
|
|
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,
|
|
74
|
-
background-color: var(--color-field-surface,
|
|
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,
|
|
77
|
+
border-inline-start: 1px solid color-mix(in oklch, var(--color-field-inverse, darkslategray) 20%, transparent);
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
package/lib/manifest.tooltip.css
CHANGED
|
@@ -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,
|
|
46
|
-
background-color: var(--color-content-stark,
|
|
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,
|
|
52
|
+
transition-delay: var(--tooltip-hover-delay, .5s)
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/* Leading icon */
|
package/lib/manifest.tooltips.js
CHANGED
|
@@ -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 ----
|