mnfst 0.5.90 → 0.5.91

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.
@@ -331,12 +331,13 @@
331
331
  inset-inline-start: 0;
332
332
  display: inline-flex;
333
333
  flex-direction: column;
334
- padding: calc(var(--spacing, 0.25rem) * 4) 0 calc(var(--spacing, 0.25rem) * 4) calc(var(--spacing, 0.25rem) * 4);
334
+ padding: calc(var(--spacing, .25rem) * 4);
335
335
  font-family: inherit;
336
336
  font-size: inherit;
337
337
  line-height: inherit;
338
338
  text-align: end;
339
339
  color: var(--color-content-subtle, darkgray);
340
+ background-color: var(--color-page, white);
340
341
  pointer-events: none;
341
342
  user-select: none
342
343
  }
@@ -391,6 +392,14 @@
391
392
 
392
393
  /* Preview frames */
393
394
  :where(aside.frame) {
395
+ font-size: unset;
396
+ line-height: unset;
397
+ font-family: unset;
398
+ font-weight: unset;
399
+ color: unset;
400
+ background: unset;
401
+ border: unset;
402
+ box-shadow: unset;
394
403
  border: 0 none;
395
404
  white-space: normal;
396
405
  white-space-collapse: collapse
@@ -1 +1 @@
1
- @import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap");:root{--icon-code-copy:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Crect width='14' height='14' x='8' y='8' rx='2' ry='2'/%3E%3Cpath d='M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2'/%3E%3C/g%3E%3C/svg%3E");--icon-code-copied:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBjbGFzcz0ibHVjaWRlIGx1Y2lkZS1jb3B5LWNoZWNrLWljb24gbHVjaWRlLWNvcHktY2hlY2siIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0ibTEyIDE1IDIgMiA0LTQiLz48cmVjdCB3aWR0aD0iMTQiIGhlaWdodD0iMTQiIHg9IjgiIHk9IjgiIHJ4PSIyIiByeT0iMiIvPjxwYXRoIGQ9Ik00IDE2Yy0xLjEgMC0yLS45LTItMlY0YzAtMS4xLjktMiAyLTJoMTBjMS4xIDAgMiAuOSAyIDIiLz48L3N2Zz4=");--icon-code-expand:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m6 9 6 6 6-6'/%3E%3C/svg%3E");--color-code-keyword:#b8860b;--color-code-string:#8b4513;--color-code-comment:gray;--color-code-function:peru;--color-code-number:sienna;--color-code-operator:#2f4f4f;--color-code-class-name:#daa520;--color-code-tag:#4682b4;--color-code-attr-name:#ff8c00;--color-code-attr-value:#8b4513;--color-code-property:sienna;--color-code-selector:#4682b4;--color-code-punctuation:#2f4f4f;--color-code-builtin:#b8860b;--color-code-constant:sienna;--color-code-boolean:sienna;--color-code-regex:#8b4513;--color-code-symbol:#daa520;--color-code-entity:#daa520;--color-code-url:sienna;--color-code-atrule:#b8860b;--color-code-rule:#4682b4;--color-code-doctype:gray;--color-code-cdata:gray;--color-code-prolog:gray;--color-code-namespace:gray;--color-code-important:#b8860b;--color-code-inserted:#228b22;--color-code-deleted:#dc143c;--color-code-char:#8b4513}.dark{--color-code-keyword:#f4a460;--color-code-string:#deb887;--color-code-comment:#8b8b8b;--color-code-function:#daa520;--color-code-number:tan;--color-code-operator:wheat;--color-code-class-name:peru;--color-code-tag:#87ceeb;--color-code-attr-name:gold;--color-code-attr-value:#deb887;--color-code-property:tan;--color-code-selector:#87ceeb;--color-code-punctuation:wheat;--color-code-builtin:#f4a460;--color-code-constant:tan;--color-code-boolean:tan;--color-code-regex:#deb887;--color-code-symbol:peru;--color-code-entity:peru;--color-code-url:tan;--color-code-atrule:#f4a460;--color-code-rule:#98fb98;--color-code-doctype:#8b8b8b;--color-code-cdata:#8b8b8b;--color-code-prolog:#8b8b8b;--color-code-namespace:#8b8b8b;--color-code-important:#f4a460;--color-code-inserted:#98fb98;--color-code-deleted:#f08080;--color-code-char:#deb887}@layer utilities{.hljs-comment{color:var(--color-code-comment)!important}.hljs-keyword{color:var(--color-code-keyword)!important}.hljs-string{color:var(--color-code-string)!important}.hljs-number{color:var(--color-code-number)!important}.hljs-literal{color:var(--color-code-constant)!important}.hljs-type{color:var(--color-code-class-name)!important}.hljs-variable{color:var(--color-code-property)!important}.hljs-variable.language_{color:var(--color-code-keyword)!important}.hljs-variable.constant_{color:var(--color-code-constant)!important}.hljs-title{color:var(--color-code-function)!important}.hljs-title.class_.inherited__{color:var(--color-code-class-name)!important}.hljs-title.function_.invoke__{color:var(--color-code-function)!important}.hljs-params{color:var(--color-code-property)!important}.hljs-doctag{color:var(--color-code-keyword)!important;font-weight:600!important}.hljs-meta{color:var(--color-code-comment)!important}.hljs-meta.keyword_,.hljs-meta.prompt_{color:var(--color-code-keyword)!important}.hljs-meta.string_{color:var(--color-code-string)!important}.hljs-section{color:var(--color-code-keyword)!important;font-weight:600!important}.hljs-name{color:var(--color-code-tag)!important}.hljs-attribute{color:var(--color-code-attr-name)!important}.hljs-bullet{color:var(--color-code-punctuation)!important}.hljs-code{color:var(--color-code-property)!important}.hljs-formula{color:var(--color-code-number)!important}.hljs-quote{color:var(--color-code-string)!important}.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo{color:var(--color-code-selector)!important}.hljs-template-tag{color:var(--color-code-tag)!important}.hljs-template-variable{color:var(--color-code-property)!important}.hljs-subst{color:var(--color-code-string)!important}}@layer components{:where(code[role=button]){height:fit-content}:where(.code-copied-icon){background-color:currentColor;display:inline-block;height:1em;mask-image:var(--icon-code-copied);mask-position:center;mask-repeat:no-repeat;mask-size:contain;width:1em}:where([x-code],[x-code-group]):not(.unstyle){position:relative;:where(header):not(:where(:is(aside.frame) header)){align-items:center;background-color:color-mix(in oklch,var(--color-content-stark,#2f4f4f) 8%,transparent);color:var(--color-content-neutral,gray);display:flex;flex-direction:row;font-family:var(--font-sans,sans-serif);font-size:inherit;min-height:var(--spacing-field-height,2.25rem);padding-inline-end:var(--spacing-field-height);padding-inline-start:calc(var(--spacing, .25rem)*4);width:100%;&:has(button[role=tab]){padding-inline-start:calc(var(--spacing, .25rem)*2)}& [role=tablist]{display:flex;flex-direction:row;overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none;&::-webkit-scrollbar{display:none}& button[role=tab]{background:transparent;border:0;color:inherit;flex-shrink:0;font-family:inherit;font-size:inherit;height:fit-content;justify-content:start;&:hover{background-color:var(--color-field-surface,color-mix(#2f4f4f 10%,transparent));color:var(--color-content-stark,#2f4f4f)}&[aria-selected=true]{color:var(--color-brand-content,#daa520);pointer-events:none;position:relative}}}}& button.copy{background-color:transparent;font-size:75%;inset-inline-end:0;position:absolute;top:0;&:hover{background-color:transparent;&:after{background-color:var(--color-field-inverse,#2f4f4f)}}&:after{background-color:var(--color-content-neutral,gray);content:"";display:block;height:.8125rem;mask-image:var(--icon-code-copy);mask-repeat:no-repeat;mask-size:contain;width:.8125rem}&.copied:after{mask-image:var(--icon-code-copied)}}:where(header)::-webkit-scrollbar{display:none}& .lines{color:var(--color-content-subtle,#a9a9a9);display:inline-flex;flex-direction:column;font-family:inherit;font-size:inherit;inset-inline-start:0;line-height:inherit;padding:calc(var(--spacing, .25rem)*4) 0 calc(var(--spacing, .25rem)*4) calc(var(--spacing, .25rem)*4);pointer-events:none;position:absolute;text-align:end;user-select:none}&:has(.lines) code{padding-inline-start:calc(var(--spacing, .25rem)*6 + 2ch)}:where(code):not(:where(:is(aside.frame) code)){flex:1 1 100%;min-width:0;white-space:pre;white-space-collapse:preserve}& button.expand{align-items:center;background:transparent;border:0;border-radius:0;color:var(--color-content-subtle,#a9a9a9);cursor:pointer;display:flex;font-family:var(--font-sans,sans-serif);font-size:inherit;height:var(--spacing-field-height,2.25rem);justify-content:center;order:99;padding:0 calc(var(--spacing, .25rem)*2);transition:var(--transition,all .05s ease-in-out);width:100%;&:hover{background:color-mix(in oklch,var(--color-content-stark,#2f4f4f) 8%,transparent);color:var(--color-content-neutral,gray)}}&[data-collapsed] .lines,&[data-collapsed] code{mask-image:linear-gradient(180deg,#000 0,#000 calc(100% - 3em),transparent calc(100% - .75em),transparent);max-height:calc(var(--collapse-lines, 20)*1.7em + var(--spacing, .25rem)*8);overflow:hidden}:where(aside.frame){border:0;white-space:normal;white-space-collapse:collapse}}:where([x-code-group]){flex-flow:column;:where([x-code]){background:transparent;border:0;border-radius:0}}:where([x-code]):has(>header){& .lines{top:var(--spacing-field-height,2.25rem)}}}
1
+ @import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap");:root{--icon-code-copy:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Crect width='14' height='14' x='8' y='8' rx='2' ry='2'/%3E%3Cpath d='M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2'/%3E%3C/g%3E%3C/svg%3E");--icon-code-copied:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBjbGFzcz0ibHVjaWRlIGx1Y2lkZS1jb3B5LWNoZWNrLWljb24gbHVjaWRlLWNvcHktY2hlY2siIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0ibTEyIDE1IDIgMiA0LTQiLz48cmVjdCB3aWR0aD0iMTQiIGhlaWdodD0iMTQiIHg9IjgiIHk9IjgiIHJ4PSIyIiByeT0iMiIvPjxwYXRoIGQ9Ik00IDE2Yy0xLjEgMC0yLS45LTItMlY0YzAtMS4xLjktMiAyLTJoMTBjMS4xIDAgMiAuOSAyIDIiLz48L3N2Zz4=");--icon-code-expand:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m6 9 6 6 6-6'/%3E%3C/svg%3E");--color-code-keyword:#b8860b;--color-code-string:#8b4513;--color-code-comment:gray;--color-code-function:peru;--color-code-number:sienna;--color-code-operator:#2f4f4f;--color-code-class-name:#daa520;--color-code-tag:#4682b4;--color-code-attr-name:#ff8c00;--color-code-attr-value:#8b4513;--color-code-property:sienna;--color-code-selector:#4682b4;--color-code-punctuation:#2f4f4f;--color-code-builtin:#b8860b;--color-code-constant:sienna;--color-code-boolean:sienna;--color-code-regex:#8b4513;--color-code-symbol:#daa520;--color-code-entity:#daa520;--color-code-url:sienna;--color-code-atrule:#b8860b;--color-code-rule:#4682b4;--color-code-doctype:gray;--color-code-cdata:gray;--color-code-prolog:gray;--color-code-namespace:gray;--color-code-important:#b8860b;--color-code-inserted:#228b22;--color-code-deleted:#dc143c;--color-code-char:#8b4513}.dark{--color-code-keyword:#f4a460;--color-code-string:#deb887;--color-code-comment:#8b8b8b;--color-code-function:#daa520;--color-code-number:tan;--color-code-operator:wheat;--color-code-class-name:peru;--color-code-tag:#87ceeb;--color-code-attr-name:gold;--color-code-attr-value:#deb887;--color-code-property:tan;--color-code-selector:#87ceeb;--color-code-punctuation:wheat;--color-code-builtin:#f4a460;--color-code-constant:tan;--color-code-boolean:tan;--color-code-regex:#deb887;--color-code-symbol:peru;--color-code-entity:peru;--color-code-url:tan;--color-code-atrule:#f4a460;--color-code-rule:#98fb98;--color-code-doctype:#8b8b8b;--color-code-cdata:#8b8b8b;--color-code-prolog:#8b8b8b;--color-code-namespace:#8b8b8b;--color-code-important:#f4a460;--color-code-inserted:#98fb98;--color-code-deleted:#f08080;--color-code-char:#deb887}@layer utilities{.hljs-comment{color:var(--color-code-comment)!important}.hljs-keyword{color:var(--color-code-keyword)!important}.hljs-string{color:var(--color-code-string)!important}.hljs-number{color:var(--color-code-number)!important}.hljs-literal{color:var(--color-code-constant)!important}.hljs-type{color:var(--color-code-class-name)!important}.hljs-variable{color:var(--color-code-property)!important}.hljs-variable.language_{color:var(--color-code-keyword)!important}.hljs-variable.constant_{color:var(--color-code-constant)!important}.hljs-title{color:var(--color-code-function)!important}.hljs-title.class_.inherited__{color:var(--color-code-class-name)!important}.hljs-title.function_.invoke__{color:var(--color-code-function)!important}.hljs-params{color:var(--color-code-property)!important}.hljs-doctag{color:var(--color-code-keyword)!important;font-weight:600!important}.hljs-meta{color:var(--color-code-comment)!important}.hljs-meta.keyword_,.hljs-meta.prompt_{color:var(--color-code-keyword)!important}.hljs-meta.string_{color:var(--color-code-string)!important}.hljs-section{color:var(--color-code-keyword)!important;font-weight:600!important}.hljs-name{color:var(--color-code-tag)!important}.hljs-attribute{color:var(--color-code-attr-name)!important}.hljs-bullet{color:var(--color-code-punctuation)!important}.hljs-code{color:var(--color-code-property)!important}.hljs-formula{color:var(--color-code-number)!important}.hljs-quote{color:var(--color-code-string)!important}.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo{color:var(--color-code-selector)!important}.hljs-template-tag{color:var(--color-code-tag)!important}.hljs-template-variable{color:var(--color-code-property)!important}.hljs-subst{color:var(--color-code-string)!important}}@layer components{:where(code[role=button]){height:fit-content}:where(.code-copied-icon){background-color:currentColor;display:inline-block;height:1em;mask-image:var(--icon-code-copied);mask-position:center;mask-repeat:no-repeat;mask-size:contain;width:1em}:where([x-code],[x-code-group]):not(.unstyle){position:relative;:where(header):not(:where(:is(aside.frame) header)){align-items:center;background-color:color-mix(in oklch,var(--color-content-stark,#2f4f4f) 8%,transparent);color:var(--color-content-neutral,gray);display:flex;flex-direction:row;font-family:var(--font-sans,sans-serif);font-size:inherit;min-height:var(--spacing-field-height,2.25rem);padding-inline-end:var(--spacing-field-height);padding-inline-start:calc(var(--spacing, .25rem)*4);width:100%;&:has(button[role=tab]){padding-inline-start:calc(var(--spacing, .25rem)*2)}& [role=tablist]{display:flex;flex-direction:row;overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none;&::-webkit-scrollbar{display:none}& button[role=tab]{background:transparent;border:0;color:inherit;flex-shrink:0;font-family:inherit;font-size:inherit;height:fit-content;justify-content:start;&:hover{background-color:var(--color-field-surface,color-mix(#2f4f4f 10%,transparent));color:var(--color-content-stark,#2f4f4f)}&[aria-selected=true]{color:var(--color-brand-content,#daa520);pointer-events:none;position:relative}}}}& button.copy{background-color:transparent;font-size:75%;inset-inline-end:0;position:absolute;top:0;&:hover{background-color:transparent;&:after{background-color:var(--color-field-inverse,#2f4f4f)}}&:after{background-color:var(--color-content-neutral,gray);content:"";display:block;height:.8125rem;mask-image:var(--icon-code-copy);mask-repeat:no-repeat;mask-size:contain;width:.8125rem}&.copied:after{mask-image:var(--icon-code-copied)}}:where(header)::-webkit-scrollbar{display:none}& .lines{background-color:var(--color-page,#fff);color:var(--color-content-subtle,#a9a9a9);display:inline-flex;flex-direction:column;font-family:inherit;font-size:inherit;inset-inline-start:0;line-height:inherit;padding:calc(var(--spacing, .25rem)*4);pointer-events:none;position:absolute;text-align:end;user-select:none}&:has(.lines) code{padding-inline-start:calc(var(--spacing, .25rem)*6 + 2ch)}:where(code):not(:where(:is(aside.frame) code)){flex:1 1 100%;min-width:0;white-space:pre;white-space-collapse:preserve}& button.expand{align-items:center;background:transparent;border:0;border-radius:0;color:var(--color-content-subtle,#a9a9a9);cursor:pointer;display:flex;font-family:var(--font-sans,sans-serif);font-size:inherit;height:var(--spacing-field-height,2.25rem);justify-content:center;order:99;padding:0 calc(var(--spacing, .25rem)*2);transition:var(--transition,all .05s ease-in-out);width:100%;&:hover{background:color-mix(in oklch,var(--color-content-stark,#2f4f4f) 8%,transparent);color:var(--color-content-neutral,gray)}}&[data-collapsed] .lines,&[data-collapsed] code{mask-image:linear-gradient(180deg,#000 0,#000 calc(100% - 3em),transparent calc(100% - .75em),transparent);max-height:calc(var(--collapse-lines, 20)*1.7em + var(--spacing, .25rem)*8);overflow:hidden}:where(aside.frame){background:unset;border:0;box-shadow:unset;color:unset;font-family:unset;font-size:unset;font-weight:unset;line-height:unset;white-space:normal;white-space-collapse:collapse}}:where([x-code-group]){flex-flow:column;:where([x-code]){background:transparent;border:0;border-radius:0}}:where([x-code]):has(>header){& .lines{top:var(--spacing-field-height,2.25rem)}}}
package/lib/manifest.css CHANGED
@@ -3534,7 +3534,7 @@
3534
3534
  border-radius: calc(var(--radius, 0.5rem) * 2);
3535
3535
  color: var(--color-content-stark, darkslategray);
3536
3536
  background-color: color-mix(in oklch, var(--color-field-surface, color-mix(darkslategray 10%, transparent)) 20%, transparent);
3537
- border: 1px solid color-mix(in oklch, var(--color-field-surface, color-mix(darkslategray 10%, transparent)) 30%, transparent);
3537
+ border: 1px solid color-mix(in oklch, var(--color-field-surface, color-mix(darkslategray 10%, transparent)) 15%, transparent);
3538
3538
 
3539
3539
  &:not(.frame) * {
3540
3540
  color: inherit
@@ -0,0 +1,535 @@
1
+ /* Manifest Export — runtime download of pages, regions, or data sources.
2
+ *
3
+ * The `x-export` directive turns its host element into a download action.
4
+ * What gets downloaded depends on the host element type:
5
+ *
6
+ * <button x-export> → click downloads the whole page
7
+ * <button x-export="{ target: '#x' }"> → click downloads the element with id="x"
8
+ * <a x-export href="#section"> → click downloads #section (no scroll-to)
9
+ * <a x-export href="/other-page"> → appends ?export=<format> to the href
10
+ * so the destination page auto-exports
11
+ * itself after navigation
12
+ *
13
+ * For the destination of a cross-page export, declare what's exportable:
14
+ *
15
+ * <div x-export="{ trigger: 'url', target: '#report' }"></div>
16
+ *
17
+ * On page load, this checks `?export=<format>` in the URL. If present and a
18
+ * known format, it fires the export against the configured target. If the
19
+ * URL has no param, the page renders normally — random visitors never
20
+ * trigger downloads.
21
+ *
22
+ * For programmatic use, the `$export` magic runs an export from any
23
+ * Alpine expression and returns a promise:
24
+ *
25
+ * <form @submit.prevent="if (valid) await $export({ format: 'csv', source: 'rows' })">
26
+ *
27
+ * Supported formats: pdf (default), png, jpeg, webp, csv, json.
28
+ *
29
+ * Library dependencies (html2canvas-pro, jsPDF) are loaded lazily on first
30
+ * use from jsDelivr; pages that never export pay nothing.
31
+ *
32
+ * Elements with `data-no-export` are excluded from visual snapshots.
33
+ */
34
+
35
+ function initializeExportPlugin() {
36
+
37
+ Alpine.directive('export', (el, { modifiers, expression }, { evaluate, cleanup }) => {
38
+
39
+ const opts = resolveOptions(expression, modifiers, evaluate);
40
+ const format = (opts.format || 'pdf').toLowerCase();
41
+ const isAnchor = el.tagName === 'A';
42
+ const href = isAnchor ? el.getAttribute('href') : null;
43
+
44
+ // ----- URL-trigger destination behavior -----
45
+ // The page declares "I'm exportable; fire if the URL says so." Runs
46
+ // once per page load, regardless of how many elements declare it.
47
+ if (opts.trigger === 'url') {
48
+ if (urlTriggerFired) return;
49
+ const paramName = opts.urlParam || 'export';
50
+ const paramValue = new URLSearchParams(window.location.search).get(paramName);
51
+ if (paramValue) {
52
+ urlTriggerFired = true;
53
+ const fmt = isKnownFormat(paramValue) ? paramValue : format;
54
+ setTimeout(() => {
55
+ runExport(fmt, opts, resolveFilename(el, opts, fmt))
56
+ .catch((err) => emitError(fmt, err));
57
+ }, Number(opts.delay) > 0 ? Number(opts.delay) : 0);
58
+ }
59
+ return;
60
+ }
61
+
62
+ // ----- Anchor with cross-page href -----
63
+ // Pre-arm the href with ?export=<format> so default browser navigation
64
+ // delivers the user to the destination URL with the export signal.
65
+ // Hover-prefetch, middle-click, right-click-copy all see the same URL.
66
+ if (isAnchor && href && !href.startsWith('#') && !href.startsWith('javascript:')
67
+ && !/^(mailto|tel):/i.test(href)) {
68
+ try {
69
+ const url = new URL(href, window.location.href);
70
+ const paramName = opts.urlParam || 'export';
71
+ url.searchParams.set(paramName, format);
72
+ // Preserve the original href shape: relative stays relative.
73
+ if (url.origin === window.location.origin && !href.startsWith('http')) {
74
+ el.setAttribute('href', url.pathname + url.search + url.hash);
75
+ } else {
76
+ el.setAttribute('href', url.toString());
77
+ }
78
+ } catch (err) {
79
+ console.warn('[x-export] could not parse href for cross-page export:', err.message);
80
+ }
81
+ return;
82
+ }
83
+
84
+ // ----- Anchor with same-page fragment href -----
85
+ // The href IS the target. Click downloads the matched element
86
+ // instead of jumping the page.
87
+ if (isAnchor && href && href.startsWith('#')) {
88
+ const onClick = async (e) => {
89
+ if (e && typeof e.preventDefault === 'function') e.preventDefault();
90
+ const filename = resolveFilename(el, opts, format);
91
+ try {
92
+ await runExport(format, { ...opts, target: href }, filename);
93
+ } catch (err) {
94
+ emitError(format, err);
95
+ }
96
+ };
97
+ el.addEventListener('click', onClick);
98
+ cleanup(() => el.removeEventListener('click', onClick));
99
+ return;
100
+ }
101
+
102
+ // ----- Default: click anywhere else triggers export -----
103
+ const onClick = async (e) => {
104
+ if (e && typeof e.preventDefault === 'function') e.preventDefault();
105
+ const filename = resolveFilename(el, opts, format);
106
+ try {
107
+ await runExport(format, opts, filename);
108
+ } catch (err) {
109
+ emitError(format, err);
110
+ }
111
+ };
112
+ el.addEventListener('click', onClick);
113
+ cleanup(() => el.removeEventListener('click', onClick));
114
+ });
115
+
116
+ // ----- $export magic — programmatic trigger from any expression -----
117
+ Alpine.magic('export', () => async (opts = {}) => {
118
+ const format = String(opts.format || 'pdf').toLowerCase();
119
+ const filename = opts.filename || defaultFilename(format);
120
+ return runExport(format, opts, filename);
121
+ });
122
+
123
+ // ------- Options + format helpers -----------------------------------
124
+
125
+ function resolveOptions(expression, modifiers, evaluate) {
126
+ let opts = {};
127
+ if (expression && expression.trim()) {
128
+ try {
129
+ const v = evaluate(expression);
130
+ if (v && typeof v === 'object') opts = { ...v };
131
+ else if (typeof v === 'string') opts.format = v;
132
+ } catch (err) {
133
+ console.warn('[x-export] could not evaluate options expression:', err.message);
134
+ }
135
+ }
136
+ if (!opts.format && Array.isArray(modifiers) && modifiers.length) {
137
+ const found = modifiers.find((m) => isKnownFormat(String(m).toLowerCase()));
138
+ if (found) opts.format = String(found).toLowerCase();
139
+ }
140
+ return opts;
141
+ }
142
+
143
+ function isKnownFormat(f) {
144
+ return f === 'pdf' || f === 'png' || f === 'jpeg' || f === 'jpg' || f === 'webp' || f === 'csv' || f === 'json';
145
+ }
146
+
147
+ function defaultFilename(format) {
148
+ const ext = format === 'jpeg' || format === 'jpg' ? 'jpg' : format;
149
+ const ts = new Date().toISOString().slice(0, 19).replace(/[:T]/g, '-');
150
+ return `export-${ts}.${ext}`;
151
+ }
152
+
153
+ // Filename resolution precedence (highest first):
154
+ // 1. opts.filename from the directive's object expression
155
+ // 2. the standard HTML `download` attribute on an anchor host
156
+ // 3. a `data-filename` attribute on any host element
157
+ // 4. a timestamped default based on the format
158
+ function resolveFilename(el, opts, format) {
159
+ if (opts && opts.filename) return String(opts.filename);
160
+ if (el && el.tagName === 'A') {
161
+ const dl = el.getAttribute('download');
162
+ if (dl) return dl;
163
+ }
164
+ if (el && typeof el.getAttribute === 'function') {
165
+ const df = el.getAttribute('data-filename');
166
+ if (df) return df;
167
+ }
168
+ return defaultFilename(format);
169
+ }
170
+
171
+ // Transparent 1×1 PNG, used as a fallback when an inline image fails to fetch.
172
+ // Without it, a single CORS or 404 image would reject the entire snapshot.
173
+ const TRANSPARENT_PIXEL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII=';
174
+
175
+ // The snapshot library rejects with the raw `image.onerror` Event when the
176
+ // assembled SVG fails to decode (oversized clones, malformed inline assets,
177
+ // etc.). Translate those into something readable before logging.
178
+ function describeExportError(err) {
179
+ if (err && err.message) return err;
180
+ if (err && typeof Event !== 'undefined' && err instanceof Event) {
181
+ const tag = err.target && err.target.tagName ? err.target.tagName.toLowerCase() : 'image';
182
+ return new Error(
183
+ `failed to render ${tag} during export. ` +
184
+ `Common causes: cross-origin images without CORS headers, ` +
185
+ `an oversized target element, or a no-target snapshot of a complex page. ` +
186
+ `Pass a "target" option to scope the snapshot.`
187
+ );
188
+ }
189
+ return new Error(String(err));
190
+ }
191
+
192
+ function emitError(format, err) {
193
+ const e = describeExportError(err);
194
+ console.error('[x-export] export failed:', e.message);
195
+ try {
196
+ window.dispatchEvent(new CustomEvent('manifest:export-error', {
197
+ detail: { format, error: e.message }
198
+ }));
199
+ } catch { /* ignore */ }
200
+ }
201
+
202
+ async function runExport(format, opts, filename) {
203
+ switch (format) {
204
+ case 'pdf': return exportPdf(opts, filename);
205
+ case 'png': return exportImage(opts, filename, 'png');
206
+ case 'jpeg':
207
+ case 'jpg': return exportImage(opts, filename, 'jpeg');
208
+ case 'webp': return exportImage(opts, filename, 'webp');
209
+ case 'csv': return exportCsv(opts, filename);
210
+ case 'json': return exportJson(opts, filename);
211
+ default: throw new Error(`Unknown format "${format}". Supported: pdf, png, jpeg, webp, csv, json.`);
212
+ }
213
+ }
214
+
215
+ // ------- Visual exports (PDF, PNG, JPEG, WebP) ----------------------
216
+
217
+ function resolveTarget(opts) {
218
+ if (opts.target) {
219
+ const t = typeof opts.target === 'string'
220
+ ? document.querySelector(opts.target)
221
+ : opts.target;
222
+ if (!t) throw new Error(`target "${opts.target}" matched no element`);
223
+ return t;
224
+ }
225
+ return document.body;
226
+ }
227
+
228
+ function snapshotOptions(opts) {
229
+ // ignoreElements callback for html2canvas-pro — exclude opt-out elements.
230
+ // (Inverse of modern-screenshot's `filter`: return true to SKIP this node.)
231
+ const ignoreElements = (node) => {
232
+ return !!(node && node.nodeType === 1
233
+ && node.hasAttribute && node.hasAttribute('data-no-export'));
234
+ };
235
+ const out = {
236
+ scale: Number(opts.resolution) > 0 ? Number(opts.resolution) : 2,
237
+ ignoreElements,
238
+ useCORS: true,
239
+ allowTaint: false,
240
+ // Bound how long we wait per cross-origin image. Failed fetches
241
+ // produce a missing image rather than rejecting the whole snapshot.
242
+ imageTimeout: 5000,
243
+ logging: false,
244
+ // Pin the capture origin to (0, 0) so the snapshot starts at the
245
+ // document top regardless of the user's current scroll position.
246
+ // Without this, html2canvas defaults scrollY to window.pageYOffset,
247
+ // which clips the snapshot to "below the current scroll" — anything
248
+ // above the fold renders as blank white space.
249
+ scrollX: 0,
250
+ scrollY: 0,
251
+ };
252
+ if (opts.backgroundColor) out.backgroundColor = opts.backgroundColor;
253
+ if (opts.width) out.width = Number(opts.width);
254
+ if (opts.height) out.height = Number(opts.height);
255
+ return out;
256
+ }
257
+
258
+ // Await all <img> loads inside the target so freshly-mounted images don't
259
+ // appear blank in the snapshot. Bounded by a timeout — a single slow image
260
+ // shouldn't stall the export indefinitely.
261
+ async function waitForImages(target, timeoutMs = 5000) {
262
+ if (!target || typeof target.querySelectorAll !== 'function') return;
263
+ const imgs = Array.from(target.querySelectorAll('img'));
264
+ const pending = imgs.filter((img) => !img.complete || img.naturalWidth === 0);
265
+ if (pending.length === 0) return;
266
+ await Promise.race([
267
+ Promise.all(pending.map((img) => new Promise((resolve) => {
268
+ img.addEventListener('load', resolve, { once: true });
269
+ img.addEventListener('error', resolve, { once: true });
270
+ }))),
271
+ new Promise((resolve) => setTimeout(resolve, timeoutMs)),
272
+ ]);
273
+ }
274
+
275
+ // html2canvas-pro paints directly to canvas via computed styles, so the
276
+ // SVG-foreignObject failure modes (oversized SVGs, @layer/oklch parsing
277
+ // issues) don't apply. The fallback path is retained for the rare case
278
+ // where the library throws (e.g. tainted canvas on cross-origin images).
279
+ async function snapshotToCanvas(lib, target, so) {
280
+ try {
281
+ return await lib(target, so);
282
+ } catch (err) {
283
+ // Retry once with allowTaint enabled and a lower scale — preserves
284
+ // the snapshot even when an image fails cross-origin policy.
285
+ const safer = { ...so, allowTaint: true, useCORS: false, scale: Math.min(so.scale || 2, 1) };
286
+ return await lib(target, safer);
287
+ }
288
+ }
289
+
290
+ async function exportImage(opts, filename, ext) {
291
+ const lib = await loadSnapshotLib();
292
+ const target = resolveTarget(opts);
293
+ const so = snapshotOptions(opts);
294
+ if ((ext === 'jpeg' || ext === 'webp') && !so.backgroundColor) {
295
+ so.backgroundColor = effectivePageBackground();
296
+ }
297
+ await waitForImages(target);
298
+ const canvas = await snapshotToCanvas(lib, target, so);
299
+ const quality = Number(opts.quality) > 0 && Number(opts.quality) <= 1
300
+ ? Number(opts.quality)
301
+ : 0.95;
302
+ let dataUrl;
303
+ if (ext === 'png') dataUrl = canvas.toDataURL('image/png');
304
+ else if (ext === 'jpeg') dataUrl = canvas.toDataURL('image/jpeg', quality);
305
+ else if (ext === 'webp') dataUrl = canvas.toDataURL('image/webp', quality);
306
+ triggerDownload(dataUrl, filename);
307
+ }
308
+
309
+ // Whole-page PDF: route through the browser's native print pipeline.
310
+ // It handles multi-page layout, page breaks, vector text, and the page's
311
+ // own @media print CSS — far more reliable than rasterizing a long page
312
+ // and embedding it as a single image. The user sees the print dialog and
313
+ // picks "Save as PDF" (or any installed PDF printer). Element-scoped PDFs
314
+ // continue to use html2canvas-pro + jsPDF.
315
+ async function exportPdf(opts, filename) {
316
+ if (!opts.target) {
317
+ return printToPdf(filename);
318
+ }
319
+ const [imgLib, jsPDFCtor] = await Promise.all([loadSnapshotLib(), loadJsPDF()]);
320
+ const target = resolveTarget(opts);
321
+ const so = snapshotOptions(opts);
322
+ if (!so.backgroundColor) so.backgroundColor = effectivePageBackground();
323
+ await waitForImages(target);
324
+ const canvas = await snapshotToCanvas(imgLib, target, so);
325
+ const dataUrl = canvas.toDataURL('image/png');
326
+ const img = await loadImage(dataUrl);
327
+ const orientation = img.width > img.height ? 'landscape' : 'portrait';
328
+ const pdf = new jsPDFCtor({ orientation, unit: 'pt', format: opts.pageSize || 'a4' });
329
+ const pageW = pdf.internal.pageSize.getWidth();
330
+ const pageH = pdf.internal.pageSize.getHeight();
331
+ const ratio = Math.min(pageW / img.width, pageH / img.height);
332
+ const renderW = img.width * ratio;
333
+ const renderH = img.height * ratio;
334
+ const offsetX = (pageW - renderW) / 2;
335
+ const offsetY = (pageH - renderH) / 2;
336
+ pdf.addImage(dataUrl, 'PNG', offsetX, offsetY, renderW, renderH);
337
+ pdf.save(filename);
338
+ }
339
+
340
+ function printToPdf(filename) {
341
+ // The browser's "Save as PDF" dialog seeds its default filename from
342
+ // document.title. Swap it briefly so the suggested name matches the
343
+ // user's intent, then restore after the dialog closes.
344
+ const original = document.title;
345
+ const cleaned = String(filename || '').replace(/\.pdf$/i, '') || original;
346
+ document.title = cleaned;
347
+ try { window.print(); }
348
+ finally {
349
+ // Wait one frame so the print dialog reads the swapped title first.
350
+ setTimeout(() => { document.title = original; }, 0);
351
+ }
352
+ }
353
+
354
+ function effectivePageBackground() {
355
+ let el = document.body;
356
+ while (el && el !== document.documentElement.parentElement) {
357
+ const cs = getComputedStyle(el);
358
+ const bg = cs.backgroundColor;
359
+ if (bg && bg !== 'rgba(0, 0, 0, 0)' && bg !== 'transparent') return bg;
360
+ el = el.parentElement;
361
+ }
362
+ return '#ffffff';
363
+ }
364
+
365
+ function loadImage(dataUrl) {
366
+ return new Promise((resolve, reject) => {
367
+ const img = new Image();
368
+ img.onload = () => resolve(img);
369
+ img.onerror = reject;
370
+ img.src = dataUrl;
371
+ });
372
+ }
373
+
374
+ // ------- Tabular exports (CSV, JSON) --------------------------------
375
+
376
+ function resolveDataset(opts) {
377
+ if (Array.isArray(opts.data)) return opts.data;
378
+ if (opts.data && typeof opts.data === 'object') return opts.data;
379
+ if (opts.source) {
380
+ const x = window.$x || (window.Alpine && window.Alpine.magic && window.Alpine.magic('x'));
381
+ if (x && x[opts.source] != null) return x[opts.source];
382
+ }
383
+ throw new Error('csv/json export needs `source: "<name>"` or `data: <value>`');
384
+ }
385
+
386
+ function exportCsv(opts, filename) {
387
+ const data = resolveDataset(opts);
388
+ if (!Array.isArray(data)) throw new Error('csv export expects an array data source');
389
+ if (data.length === 0) {
390
+ triggerDownload(blobUrl('', 'text/csv'), filename);
391
+ return;
392
+ }
393
+ const headers = [];
394
+ const seen = new Set();
395
+ for (const row of data) {
396
+ if (!row || typeof row !== 'object') continue;
397
+ for (const k of Object.keys(row)) {
398
+ if (!seen.has(k)) { seen.add(k); headers.push(k); }
399
+ }
400
+ }
401
+ const lines = [headers.map(csvCell).join(',')];
402
+ for (const row of data) {
403
+ lines.push(headers.map((h) => csvCell(row && row[h])).join(','));
404
+ }
405
+ triggerDownload(blobUrl(lines.join('\n') + '\n', 'text/csv;charset=utf-8'), filename);
406
+ }
407
+
408
+ function exportJson(opts, filename) {
409
+ const data = resolveDataset(opts);
410
+ const serializable = sanitize(data);
411
+ triggerDownload(blobUrl(JSON.stringify(serializable, null, 2), 'application/json'), filename);
412
+ }
413
+
414
+ function sanitize(value) {
415
+ if (Array.isArray(value)) return value.map(sanitize);
416
+ if (value && typeof value === 'object') {
417
+ const out = {};
418
+ for (const k of Object.keys(value)) {
419
+ if (k.startsWith('$') || k === '_loading' || k === '_error') continue;
420
+ out[k] = sanitize(value[k]);
421
+ }
422
+ return out;
423
+ }
424
+ return value;
425
+ }
426
+
427
+ function csvCell(v) {
428
+ if (v == null) return '';
429
+ const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
430
+ return /[",\n\r]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
431
+ }
432
+
433
+ // ------- Helpers ----------------------------------------------------
434
+
435
+ function blobUrl(content, type) {
436
+ return URL.createObjectURL(new Blob([content], { type }));
437
+ }
438
+
439
+ function triggerDownload(url, filename) {
440
+ const a = document.createElement('a');
441
+ a.href = url;
442
+ a.download = filename;
443
+ document.body.appendChild(a);
444
+ a.click();
445
+ a.remove();
446
+ if (url.startsWith('blob:')) setTimeout(() => { try { URL.revokeObjectURL(url); } catch { /* ignore */ } }, 2000);
447
+ }
448
+
449
+ // Lazy library loaders — cached promises.
450
+
451
+ // html2canvas-pro paints directly to a canvas by walking computed styles,
452
+ // rather than cloning the DOM into an SVG foreignObject. That means it has
453
+ // no failure modes around @layer rules, oklch() colors, url(data:...) in
454
+ // cross-origin stylesheets, or oversized SVG decoding — the cases that
455
+ // sank both html-to-image and modern-screenshot on real-world pages.
456
+ // ESM-only, so we load via dynamic import() from jsDelivr.
457
+ let snapshotLibPromise = null;
458
+ function loadSnapshotLib() {
459
+ if (snapshotLibPromise) return snapshotLibPromise;
460
+ snapshotLibPromise = import('https://cdn.jsdelivr.net/npm/html2canvas-pro@2/dist/html2canvas-pro.esm.js')
461
+ .then((mod) => {
462
+ const fn = mod.default || mod.html2canvas;
463
+ if (typeof fn !== 'function') {
464
+ throw new Error('html2canvas-pro failed to load (missing default export)');
465
+ }
466
+ return fn;
467
+ })
468
+ .catch((err) => {
469
+ snapshotLibPromise = null; // allow retry on next call
470
+ throw err;
471
+ });
472
+ return snapshotLibPromise;
473
+ }
474
+
475
+ let jsPDFPromise = null;
476
+ function loadJsPDF() {
477
+ if (jsPDFPromise) return jsPDFPromise;
478
+ jsPDFPromise = loadScript('https://cdn.jsdelivr.net/npm/jspdf@2.5.2/dist/jspdf.umd.min.js')
479
+ .then(() => {
480
+ const ctor = window.jspdf && window.jspdf.jsPDF;
481
+ if (!ctor) throw new Error('jsPDF failed to load');
482
+ return ctor;
483
+ });
484
+ return jsPDFPromise;
485
+ }
486
+
487
+ function loadScript(src) {
488
+ return new Promise((resolve, reject) => {
489
+ const existing = document.querySelector(`script[src="${src}"]`);
490
+ if (existing) {
491
+ if (existing._loaded) resolve();
492
+ else {
493
+ existing.addEventListener('load', () => resolve());
494
+ existing.addEventListener('error', reject);
495
+ }
496
+ return;
497
+ }
498
+ const s = document.createElement('script');
499
+ s.src = src;
500
+ s.async = true;
501
+ s.addEventListener('load', () => { s._loaded = true; resolve(); });
502
+ s.addEventListener('error', () => reject(new Error('Failed to load ' + src)));
503
+ document.head.appendChild(s);
504
+ });
505
+ }
506
+ }
507
+
508
+ // Module-level guard: URL-triggered exports fire at most once per page load,
509
+ // regardless of how many elements declare `trigger: 'url'`.
510
+ let urlTriggerFired = false;
511
+
512
+ // Standard plugin init lifecycle.
513
+ let exportPluginInitialized = false;
514
+ function ensureExportPluginInitialized() {
515
+ if (exportPluginInitialized) return;
516
+ if (!window.Alpine || typeof window.Alpine.directive !== 'function') return;
517
+ exportPluginInitialized = true;
518
+ initializeExportPlugin();
519
+ }
520
+ window.ensureExportPluginInitialized = ensureExportPluginInitialized;
521
+ if (document.readyState === 'loading') {
522
+ document.addEventListener('DOMContentLoaded', ensureExportPluginInitialized);
523
+ }
524
+ document.addEventListener('alpine:init', ensureExportPluginInitialized);
525
+ if (window.Alpine && typeof window.Alpine.directive === 'function') {
526
+ setTimeout(ensureExportPluginInitialized, 0);
527
+ } else {
528
+ const checkAlpine = setInterval(() => {
529
+ if (window.Alpine && typeof window.Alpine.directive === 'function') {
530
+ clearInterval(checkAlpine);
531
+ ensureExportPluginInitialized();
532
+ }
533
+ }, 10);
534
+ setTimeout(() => clearInterval(checkAlpine), 5000);
535
+ }
@@ -8,8 +8,9 @@
8
8
  "manifest.components.js": "sha384-3dCTD5EwCZTiX+1obYtDNM3WWwPh2JDQUQQsdRUUK3gs6FXjse1ShkKaT/2jsNaI",
9
9
  "manifest.data.js": "sha384-+wfMPBlMsmLJ7EJWGJMTKGAhLaLCyOVX+Nq+ps3Lly58QC9Dp3XRHK5yangII0yq",
10
10
  "manifest.dropdowns.js": "sha384-WMrFoSpKfJuo81dyrwhVrDO8rq+rDwh2x8x4nH01BY5ZHkvjE+/SaT2gWCI0zOn+",
11
+ "manifest.export.js": "sha384-qvdGz1TiGEDOeWJ5os1z03RURdKX+ezZEQ1KyV+9iC7X0esLK83mtY87t4MQv45t",
11
12
  "manifest.icons.js": "sha384-uOkboYrovjCpl22eey3Jaxpey+pOnot5NDnRRumcRxiR7IOVaRh1i20gYnWXR5dW",
12
- "manifest.localization.js": "sha384-eKdBIMEAwsugPP2p2fuPzQUkU44f1+Y0JgukMJ1KXLQY1/AYvpcGsEiritVDElsN",
13
+ "manifest.localization.js": "sha384-eR418BsWisFmFnmhNnNEqx5SMUJywngz6MjWJW7EQWAu1PilepjO/Rp99Z2MTbH5",
13
14
  "manifest.markdown.js": "sha384-3LgPiHrftPqAIJGhxi87C0TtfJXbsH0Qj4JmfmYgV4y5UjSx7nVSP+ppsRUWT0Xs",
14
15
  "manifest.resize.js": "sha384-Ak5gf44ERfh9pOSAD1qZzJSysslpwBCkevIlz7R3dszTUyzUKGKGF4pn5arOtgG0",
15
16
  "manifest.router.js": "sha384-n6xmIfWnYzd/0kkVTFuHhFzHuxiDgZ1Lg1W0yB6/w3Myw5pQ6PgE6SJBHfVsO7/D",
@@ -20,6 +21,6 @@
20
21
  "manifest.toasts.js": "sha384-ytd5rDbax/Ou9z23uedFXPZbxDPsk2E/pxCTq4WLvfv+os1qTI6kELp0kPp07g24",
21
22
  "manifest.tooltips.js": "sha384-Hhip5ZN66xhDw3m0XBrKLKLpcVRz3Z9RszPKqo6xvFF0mrUgQBVZ+mZjZsXgOOjS",
22
23
  "manifest.url.parameters.js": "sha384-FIufiClqDx1rJpU/QUc9z/D43qClQ6Qm8rBahipbJl9BDHUvhrOsUDegmTWW7Tuf",
23
- "manifest.utilities.js": "sha384-Q98oZClq/iRKFmuwHolisLgEitsTZiEPHxUW29liKlnL1Gx+YGq8MMivYbDlGDD6",
24
- "manifest.js": "sha384-+VqLvjMEa28w5p6vlp4OEI6w/wr9Wpuzgk4rJcWsg1aDodhEB6E30yDpgs/6zRsR"
24
+ "manifest.utilities.js": "sha384-JzIkeYogrK/qabMCyBx3RQc9j+QYGGsgVZVQGrFNqoLMhEVUSogEqap2JqHlruyk",
25
+ "manifest.js": "sha384-CUFN8gbtQW6+fnxiBnx0nPw6kKDA21+He0KFoDE9FabfJPR4lKRXaT2PWpOOKUS+"
25
26
  }
package/lib/manifest.js CHANGED
@@ -199,7 +199,8 @@
199
199
  'slides',
200
200
  'resize',
201
201
  'colorpicker',
202
- 'url-parameters'
202
+ 'url-parameters',
203
+ 'export'
203
204
  ];
204
205
 
205
206
  // Appwrite integration plugins (opt-in only, never auto-loaded)