@abcnews/components-builder 0.0.1
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/README.md +134 -0
- package/dist/BuilderStyleRoot/BuilderStyleRoot.svelte +175 -0
- package/dist/BuilderStyleRoot/BuilderStyleRoot.svelte.d.ts +5 -0
- package/dist/ContextMenu/ContextMenu.stories.svelte +66 -0
- package/dist/ContextMenu/ContextMenu.stories.svelte.d.ts +27 -0
- package/dist/ContextMenu/ContextMenu.svelte +111 -0
- package/dist/ContextMenu/ContextMenu.svelte.d.ts +7 -0
- package/dist/GoogleDocScrollyteller/GoogleDocScrollyteller.stories.svelte +24 -0
- package/dist/GoogleDocScrollyteller/GoogleDocScrollyteller.stories.svelte.d.ts +27 -0
- package/dist/GoogleDocScrollyteller/GoogleDocScrollyteller.svelte +211 -0
- package/dist/GoogleDocScrollyteller/GoogleDocScrollyteller.svelte.d.ts +9 -0
- package/dist/GoogleDocScrollyteller/utils.d.ts +23 -0
- package/dist/GoogleDocScrollyteller/utils.js +94 -0
- package/dist/Modal/Modal.stories.svelte +32 -0
- package/dist/Modal/Modal.stories.svelte.d.ts +27 -0
- package/dist/Modal/Modal.svelte +116 -0
- package/dist/Modal/Modal.svelte.d.ts +8 -0
- package/dist/ScreenshotTool/ScreenshotTool.stories.svelte +26 -0
- package/dist/ScreenshotTool/ScreenshotTool.stories.svelte.d.ts +27 -0
- package/dist/ScreenshotTool/ScreenshotTool.svelte +292 -0
- package/dist/ScreenshotTool/ScreenshotTool.svelte.d.ts +17 -0
- package/dist/Typeahead/Typeahead.stories.svelte +26 -0
- package/dist/Typeahead/Typeahead.stories.svelte.d.ts +27 -0
- package/dist/Typeahead/Typeahead.svelte +177 -0
- package/dist/Typeahead/Typeahead.svelte.d.ts +12 -0
- package/dist/UpdateChecker/UpdateChecker.stories.svelte +27 -0
- package/dist/UpdateChecker/UpdateChecker.stories.svelte.d.ts +27 -0
- package/dist/UpdateChecker/UpdateChecker.svelte +127 -0
- package/dist/UpdateChecker/UpdateChecker.svelte.d.ts +11 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/package.json +71 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Component } from "svelte";
|
|
3
|
+
/**
|
|
4
|
+
* @file
|
|
5
|
+
* Svelte port of google-doc-scrollyteller
|
|
6
|
+
*/
|
|
7
|
+
import { loadData } from "./utils";
|
|
8
|
+
import { stringify } from "@abcnews/alternating-case-to-object";
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
name = "myscrollyteller",
|
|
12
|
+
markerName = "mark",
|
|
13
|
+
ScrollytellerRoot,
|
|
14
|
+
}: {
|
|
15
|
+
name: string;
|
|
16
|
+
markerName: string;
|
|
17
|
+
ScrollytellerRoot: Component;
|
|
18
|
+
} = $props();
|
|
19
|
+
|
|
20
|
+
const localStorageKey = `ABC_NEWS_BUILDER_GDOC_PREVIEW`;
|
|
21
|
+
|
|
22
|
+
let doc = $state(
|
|
23
|
+
new URLSearchParams(window.location.search.slice(1)).get("doc"),
|
|
24
|
+
);
|
|
25
|
+
let scrollytellerDefinition = $state();
|
|
26
|
+
let title = $state("");
|
|
27
|
+
let panelData = $state({});
|
|
28
|
+
let textboxValue = $state(
|
|
29
|
+
(() => {
|
|
30
|
+
try {
|
|
31
|
+
// migrate keys
|
|
32
|
+
const legacyKey = "last-successfully-loaded-google-doc-url";
|
|
33
|
+
const oldVar = localStorage[legacyKey];
|
|
34
|
+
if (oldVar) {
|
|
35
|
+
localStorage[localStorageKey] = oldVar;
|
|
36
|
+
delete localStorage[legacyKey];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// try to load our localStorage value, or fall back to the React version of the var
|
|
40
|
+
return localStorage[localStorageKey];
|
|
41
|
+
} catch (e) {
|
|
42
|
+
return "";
|
|
43
|
+
}
|
|
44
|
+
})(),
|
|
45
|
+
);
|
|
46
|
+
let error = $state("");
|
|
47
|
+
|
|
48
|
+
// Valid url looks like:
|
|
49
|
+
// https://docs.google.com/document/d/e/2PACX-1vQHeIm0sZTn4y_wQYTCV733ekALS_j1IDSJ63qsgny4gHSIjy-aOYEnHi6EFc4OvOdjF_8CdLm95bFl/pub
|
|
50
|
+
const isValid = (url) =>
|
|
51
|
+
url.match(/\/pub$/) && url.includes("docs.google.com");
|
|
52
|
+
|
|
53
|
+
$effect(() => {
|
|
54
|
+
if (!doc) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!isValid(doc)) {
|
|
59
|
+
doc = "";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
loadData({
|
|
63
|
+
name,
|
|
64
|
+
url: doc,
|
|
65
|
+
markerName,
|
|
66
|
+
})
|
|
67
|
+
.then((data) => {
|
|
68
|
+
title = data.title;
|
|
69
|
+
scrollytellerDefinition = data?.scrollytellerDefinition;
|
|
70
|
+
})
|
|
71
|
+
.catch((e) => {
|
|
72
|
+
error = e.message;
|
|
73
|
+
doc = "";
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<svelte:head>
|
|
79
|
+
{#if scrollytellerDefinition}
|
|
80
|
+
<title>{title} - Preview Google doc</title>
|
|
81
|
+
{/if}
|
|
82
|
+
|
|
83
|
+
<link
|
|
84
|
+
rel="icon"
|
|
85
|
+
type="image/png"
|
|
86
|
+
href={`data:image/svg+xml,${`
|
|
87
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-richtext" viewBox="0 0 16 16">
|
|
88
|
+
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5z"/>
|
|
89
|
+
<path d="M4.5 12.5A.5.5 0 0 1 5 12h3a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5m0-2A.5.5 0 0 1 5 10h6a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5m1.639-3.708 1.33.886 1.854-1.855a.25.25 0 0 1 .289-.047l1.888.974V8.5a.5.5 0 0 1-.5.5H5a.5.5 0 0 1-.5-.5V8s1.54-1.274 1.639-1.208M6.25 6a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5"/>
|
|
90
|
+
</svg>`.replace(/\n/, " ")}`}
|
|
91
|
+
/>
|
|
92
|
+
</svelte:head>
|
|
93
|
+
|
|
94
|
+
{#if doc}
|
|
95
|
+
<!-- A tall div reserves space so when we press "back" we return ot the same spot in the scrolly -->
|
|
96
|
+
<div style:min-height={"10000vh"} class="scrolly-root">
|
|
97
|
+
{#if scrollytellerDefinition}
|
|
98
|
+
<ScrollytellerRoot
|
|
99
|
+
panels={scrollytellerDefinition.panels}
|
|
100
|
+
onMarker={(d) => {
|
|
101
|
+
// if we send d.originalData we can re-stringify it to open this graphic directly in the builder.
|
|
102
|
+
panelData = d;
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
<div class="floaty">
|
|
106
|
+
<button
|
|
107
|
+
onclick={() => {
|
|
108
|
+
window.location.search = "";
|
|
109
|
+
}}>Load another doc</button
|
|
110
|
+
>
|
|
111
|
+
<button
|
|
112
|
+
onclick={() => {
|
|
113
|
+
const url = `${window.location.pathname.split("/").slice(0, -2).join("/")}/builder/#${stringify(panelData.originalData || {})}`;
|
|
114
|
+
// @ts-ignore
|
|
115
|
+
window.location = url;
|
|
116
|
+
}}>Open in builder</button
|
|
117
|
+
>
|
|
118
|
+
</div>
|
|
119
|
+
{/if}
|
|
120
|
+
</div>
|
|
121
|
+
{/if}
|
|
122
|
+
|
|
123
|
+
{#if error || !doc}
|
|
124
|
+
<form method="GET">
|
|
125
|
+
<fieldset class="builder__spacious">
|
|
126
|
+
<legend>Google Doc preview</legend>
|
|
127
|
+
<p>
|
|
128
|
+
Enter a <em>published</em> Google Doc url to preview the scrollyteller.
|
|
129
|
+
</p>
|
|
130
|
+
<p>
|
|
131
|
+
<small>
|
|
132
|
+
You can publish & find the URL in Google Docs through File → Share
|
|
133
|
+
→ Publish to web
|
|
134
|
+
</small>
|
|
135
|
+
</p>
|
|
136
|
+
{#if error}
|
|
137
|
+
<p class="error">
|
|
138
|
+
Error: {error}
|
|
139
|
+
</p>
|
|
140
|
+
{/if}
|
|
141
|
+
<label>
|
|
142
|
+
URL
|
|
143
|
+
<input name="doc" type="text" bind:value={textboxValue} />
|
|
144
|
+
</label>
|
|
145
|
+
<div class="builder__submit-row">
|
|
146
|
+
<button
|
|
147
|
+
onclick={(e) => {
|
|
148
|
+
if (!isValid(textboxValue)) {
|
|
149
|
+
e.preventDefault();
|
|
150
|
+
$error =
|
|
151
|
+
"This document doesn't look valid. Please ensure you published the doc & provided the public url.";
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
localStorage[localStorageKey] = textboxValue;
|
|
156
|
+
} catch (e) {}
|
|
157
|
+
}}
|
|
158
|
+
>
|
|
159
|
+
Load scrollyteller
|
|
160
|
+
</button>
|
|
161
|
+
</div>
|
|
162
|
+
</fieldset>
|
|
163
|
+
</form>
|
|
164
|
+
{/if}
|
|
165
|
+
|
|
166
|
+
<style>
|
|
167
|
+
:global(body) {
|
|
168
|
+
font-family: ABCSans, sans-serif;
|
|
169
|
+
}
|
|
170
|
+
form {
|
|
171
|
+
position: absolute;
|
|
172
|
+
left: 50%;
|
|
173
|
+
top: 30%;
|
|
174
|
+
max-width: 32rem;
|
|
175
|
+
translate: -50% -50%;
|
|
176
|
+
}
|
|
177
|
+
fieldset {
|
|
178
|
+
display: flex;
|
|
179
|
+
gap: 0.5rem;
|
|
180
|
+
flex-direction: column;
|
|
181
|
+
}
|
|
182
|
+
label {
|
|
183
|
+
display: flex;
|
|
184
|
+
gap: 0.5rem;
|
|
185
|
+
align-items: center;
|
|
186
|
+
margin-top: 0.75rem;
|
|
187
|
+
}
|
|
188
|
+
p {
|
|
189
|
+
margin: 0;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.error {
|
|
193
|
+
border: 1px solid rgb(255, 153, 0);
|
|
194
|
+
background: rgba(255, 128, 0, 0.05);
|
|
195
|
+
border-radius: 4px;
|
|
196
|
+
padding: 0.5rem;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.floaty {
|
|
200
|
+
position: fixed;
|
|
201
|
+
top: 0.5rem;
|
|
202
|
+
right: 0.5rem;
|
|
203
|
+
z-index: 10;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.scrolly-root {
|
|
207
|
+
background: white;
|
|
208
|
+
color: black;
|
|
209
|
+
border-radius: 0.2rem;
|
|
210
|
+
}
|
|
211
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Component } from "svelte";
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
name: string;
|
|
4
|
+
markerName: string;
|
|
5
|
+
ScrollytellerRoot: Component;
|
|
6
|
+
};
|
|
7
|
+
declare const GoogleDocScrollyteller: Component<$$ComponentProps, {}, "">;
|
|
8
|
+
type GoogleDocScrollyteller = ReturnType<typeof GoogleDocScrollyteller>;
|
|
9
|
+
export default GoogleDocScrollyteller;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @param options
|
|
4
|
+
* @param {string} options.name - Name in the scrollyteller opener tag. E.g. `#scrollytellerNAMEelectionmap1` would be "electionmap"
|
|
5
|
+
* @param {string} options.className - Deprecated class to apply to every panel. This can be done at the top level scrollyteller nowadays.
|
|
6
|
+
* @param {string} options.markerName - What does a #mark look like?
|
|
7
|
+
* @param {function} [preprocessCoreEl] -
|
|
8
|
+
* @param {function} [postprocessScrollytellerDefinition] -
|
|
9
|
+
* @returns
|
|
10
|
+
*/
|
|
11
|
+
export declare function loadData({ name, className, markerName, url, preprocessCoreEl, postprocessScrollytellerDefinition }: {
|
|
12
|
+
name: any;
|
|
13
|
+
className?: string | undefined;
|
|
14
|
+
markerName?: string | undefined;
|
|
15
|
+
url: any;
|
|
16
|
+
preprocessCoreEl?: ((el: any) => any) | undefined;
|
|
17
|
+
postprocessScrollytellerDefinition?: ((a: any) => any) | undefined;
|
|
18
|
+
}): Promise<{
|
|
19
|
+
title: string | null | undefined;
|
|
20
|
+
coreText: string;
|
|
21
|
+
coreHTML: string;
|
|
22
|
+
scrollytellerDefinition: import("@abcnews/svelte-scrollyteller/dist/types").ScrollytellerDefinition;
|
|
23
|
+
} | undefined>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { loadScrollyteller } from '@abcnews/svelte-scrollyteller';
|
|
2
|
+
function mountTextToMountEl(mountText) {
|
|
3
|
+
const mountEl = document.createElement('div');
|
|
4
|
+
mountEl.setAttribute('data-mount', '');
|
|
5
|
+
mountEl.setAttribute('data-component', 'Anchor');
|
|
6
|
+
mountEl.setAttribute('id', mountText.slice(1));
|
|
7
|
+
return mountEl;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
*
|
|
11
|
+
* @param options
|
|
12
|
+
* @param {string} options.name - Name in the scrollyteller opener tag. E.g. `#scrollytellerNAMEelectionmap1` would be "electionmap"
|
|
13
|
+
* @param {string} options.className - Deprecated class to apply to every panel. This can be done at the top level scrollyteller nowadays.
|
|
14
|
+
* @param {string} options.markerName - What does a #mark look like?
|
|
15
|
+
* @param {function} [preprocessCoreEl] -
|
|
16
|
+
* @param {function} [postprocessScrollytellerDefinition] -
|
|
17
|
+
* @returns
|
|
18
|
+
*/
|
|
19
|
+
export async function loadData({ name, className = 'u-full', markerName = 'mark', url, preprocessCoreEl = el => el, postprocessScrollytellerDefinition = a => a }) {
|
|
20
|
+
if (!url) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const pubURL = url.replace(/\/[^/]+?$/, '/pub');
|
|
24
|
+
const html = await fetch(pubURL).then(response => response.text());
|
|
25
|
+
const dom = new DOMParser().parseFromString(html, 'text/html');
|
|
26
|
+
const body = dom.querySelector('#contents > div');
|
|
27
|
+
const title = dom.querySelector('title')?.textContent;
|
|
28
|
+
if (!body) {
|
|
29
|
+
throw new Error('Body not found');
|
|
30
|
+
}
|
|
31
|
+
Array.from(body.querySelectorAll('*')).forEach(el => {
|
|
32
|
+
el.removeAttribute('class');
|
|
33
|
+
el.removeAttribute('id');
|
|
34
|
+
});
|
|
35
|
+
const coreEls = Array.from(body.children).map(preprocessCoreEl);
|
|
36
|
+
const coreText = coreEls.reduce((memo, el) => {
|
|
37
|
+
const text = String(el.textContent).trim();
|
|
38
|
+
memo = `${memo}\n${text ? `\n${text}` : ''}`;
|
|
39
|
+
return memo;
|
|
40
|
+
}, '');
|
|
41
|
+
const coreHTML = coreEls.reduce((memo, el) => {
|
|
42
|
+
const text = String(el.textContent).trim();
|
|
43
|
+
const html = el.outerHTML;
|
|
44
|
+
memo = `${memo}${text ? `${html}` : ''}`;
|
|
45
|
+
return memo;
|
|
46
|
+
}, '');
|
|
47
|
+
const { scrollytellingEls } = coreEls.reduce((memo, el) => {
|
|
48
|
+
if (memo.hasEnded) {
|
|
49
|
+
return memo;
|
|
50
|
+
}
|
|
51
|
+
const text = String(el.textContent).trim();
|
|
52
|
+
if (text.indexOf('#remove') === 0) {
|
|
53
|
+
memo.isRemoving = true;
|
|
54
|
+
}
|
|
55
|
+
else if (text.indexOf('#endremove') === 0) {
|
|
56
|
+
memo.isRemoving = false;
|
|
57
|
+
}
|
|
58
|
+
else if (text.indexOf('#') === 0) {
|
|
59
|
+
if (text.indexOf(`#scrollyteller${name ? `NAME${name}` : ''}`) === 0 && !memo.hasBegun) {
|
|
60
|
+
memo.hasBegun = true;
|
|
61
|
+
}
|
|
62
|
+
else if (text.indexOf('#endscrollyteller') === 0) {
|
|
63
|
+
memo.hasEnded = true;
|
|
64
|
+
}
|
|
65
|
+
memo.scrollytellingEls.push(mountTextToMountEl(text));
|
|
66
|
+
}
|
|
67
|
+
else if (!memo.hasBegun || memo.isRemoving || text === '') {
|
|
68
|
+
// skip
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
memo.scrollytellingEls.push(el);
|
|
72
|
+
}
|
|
73
|
+
return memo;
|
|
74
|
+
}, {
|
|
75
|
+
hasBegun: false,
|
|
76
|
+
hasEnded: false,
|
|
77
|
+
isRemoving: false,
|
|
78
|
+
scrollytellingEls: []
|
|
79
|
+
});
|
|
80
|
+
const container = document.createElement('div');
|
|
81
|
+
scrollytellingEls.forEach(scrollytellingEl => container.appendChild(scrollytellingEl));
|
|
82
|
+
document.body.appendChild(container);
|
|
83
|
+
let scrollytellerDefinition = loadScrollyteller(name, className, markerName);
|
|
84
|
+
document.body.removeChild(container);
|
|
85
|
+
if (postprocessScrollytellerDefinition) {
|
|
86
|
+
scrollytellerDefinition = postprocessScrollytellerDefinition(scrollytellerDefinition);
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
title,
|
|
90
|
+
coreText,
|
|
91
|
+
coreHTML,
|
|
92
|
+
scrollytellerDefinition
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script module>
|
|
2
|
+
import { defineMeta } from '@storybook/addon-svelte-csf';
|
|
3
|
+
import Modal from './Modal.svelte';
|
|
4
|
+
import BuilderStyleRoot from '../BuilderStyleRoot/BuilderStyleRoot.svelte';
|
|
5
|
+
|
|
6
|
+
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories
|
|
7
|
+
const { Story } = defineMeta({
|
|
8
|
+
title: 'Example/Modal',
|
|
9
|
+
component: Modal,
|
|
10
|
+
tags: ['autodocs'],
|
|
11
|
+
argTypes: {},
|
|
12
|
+
args: {},
|
|
13
|
+
decorators: [() => BuilderStyleRoot]
|
|
14
|
+
});
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
{#snippet children()}
|
|
18
|
+
Hello this is a modal
|
|
19
|
+
{/snippet}
|
|
20
|
+
{#snippet footerChildren()}
|
|
21
|
+
<button>Ok!</button>
|
|
22
|
+
{/snippet}
|
|
23
|
+
|
|
24
|
+
<!-- More on writing stories with args: https://storybook.js.org/docs/writing-stories/args -->
|
|
25
|
+
<Story
|
|
26
|
+
name="Primary"
|
|
27
|
+
args={{
|
|
28
|
+
children,
|
|
29
|
+
footerChildren,
|
|
30
|
+
title: 'Example modal'
|
|
31
|
+
}}
|
|
32
|
+
/>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export default Modal;
|
|
2
|
+
type Modal = SvelteComponent<{
|
|
3
|
+
[x: string]: never;
|
|
4
|
+
}, {
|
|
5
|
+
[evt: string]: CustomEvent<any>;
|
|
6
|
+
}, {}> & {
|
|
7
|
+
$$bindings?: string | undefined;
|
|
8
|
+
};
|
|
9
|
+
declare const Modal: $$__sveltets_2_IsomorphicComponent<{
|
|
10
|
+
[x: string]: never;
|
|
11
|
+
}, {
|
|
12
|
+
[evt: string]: CustomEvent<any>;
|
|
13
|
+
}, {}, {}, string>;
|
|
14
|
+
import Modal from './Modal.svelte';
|
|
15
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
16
|
+
new (options: import("svelte").ComponentConstructorOptions<Props>): import("svelte").SvelteComponent<Props, Events, Slots> & {
|
|
17
|
+
$$bindings?: Bindings;
|
|
18
|
+
} & Exports;
|
|
19
|
+
(internal: unknown, props: {
|
|
20
|
+
$$events?: Events;
|
|
21
|
+
$$slots?: Slots;
|
|
22
|
+
}): Exports & {
|
|
23
|
+
$set?: any;
|
|
24
|
+
$on?: any;
|
|
25
|
+
};
|
|
26
|
+
z_$$bindings?: Bindings;
|
|
27
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from 'svelte';
|
|
3
|
+
import { X } from 'svelte-bootstrap-icons';
|
|
4
|
+
|
|
5
|
+
let { children, footerChildren, title = '', onClose = () => {} } = $props();
|
|
6
|
+
let dialogEl = $state<HTMLDialogElement | undefined>();
|
|
7
|
+
let rect = $state<DOMRect>();
|
|
8
|
+
$effect(() => {
|
|
9
|
+
if (dialogEl) {
|
|
10
|
+
rect = dialogEl.getBoundingClientRect();
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
onMount(() => {
|
|
14
|
+
dialogEl?.showModal();
|
|
15
|
+
return () => {
|
|
16
|
+
dialogEl?.close();
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
/* Proxy required otherwise Typescript complains */
|
|
21
|
+
function onCloseTypescriptProxy() {
|
|
22
|
+
onClose();
|
|
23
|
+
}
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
27
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
28
|
+
<dialog
|
|
29
|
+
class="modal"
|
|
30
|
+
bind:this={dialogEl}
|
|
31
|
+
onclick={onCloseTypescriptProxy}
|
|
32
|
+
onclose={onCloseTypescriptProxy}
|
|
33
|
+
>
|
|
34
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
35
|
+
<div onclick={(e) => e.stopPropagation()}>
|
|
36
|
+
{#if title}
|
|
37
|
+
<div class="modal-title">
|
|
38
|
+
<h1>
|
|
39
|
+
{title}
|
|
40
|
+
</h1>
|
|
41
|
+
<button class="modal-close" onclick={onCloseTypescriptProxy}>
|
|
42
|
+
<X />
|
|
43
|
+
</button>
|
|
44
|
+
</div>
|
|
45
|
+
{/if}
|
|
46
|
+
|
|
47
|
+
<div class="modal-content">
|
|
48
|
+
{@render children?.()}
|
|
49
|
+
</div>
|
|
50
|
+
{#if footerChildren}
|
|
51
|
+
<div class="modal-footer">
|
|
52
|
+
{@render footerChildren?.()}
|
|
53
|
+
</div>
|
|
54
|
+
{/if}
|
|
55
|
+
</div>
|
|
56
|
+
</dialog>
|
|
57
|
+
|
|
58
|
+
<style>
|
|
59
|
+
.modal {
|
|
60
|
+
left: 50%;
|
|
61
|
+
top: 50%;
|
|
62
|
+
transform: translate(-50%, -50%);
|
|
63
|
+
margin: 0;
|
|
64
|
+
padding: 0;
|
|
65
|
+
background: var(--background);
|
|
66
|
+
color: var(--text);
|
|
67
|
+
border: 1px solid var(--border);
|
|
68
|
+
border-radius: 0.5rem;
|
|
69
|
+
animation: fadein 0.2s;
|
|
70
|
+
&::backdrop {
|
|
71
|
+
animation: fadein 0.2s;
|
|
72
|
+
background: rgba(0, 0, 0, 0.8);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.modal-title {
|
|
77
|
+
display: flex;
|
|
78
|
+
align-items: center;
|
|
79
|
+
justify-content: center;
|
|
80
|
+
padding: 0.5rem 0.5rem 0.5rem 1rem;
|
|
81
|
+
gap: 1rem;
|
|
82
|
+
border-bottom: 1px solid var(--border);
|
|
83
|
+
}
|
|
84
|
+
.modal-title h1 {
|
|
85
|
+
font-size: 1rem;
|
|
86
|
+
margin: 0;
|
|
87
|
+
padding: 0;
|
|
88
|
+
flex: 1;
|
|
89
|
+
}
|
|
90
|
+
button.modal-close {
|
|
91
|
+
width: 2rem;
|
|
92
|
+
border: none;
|
|
93
|
+
height: 2rem;
|
|
94
|
+
display: flex;
|
|
95
|
+
justify-content: center;
|
|
96
|
+
align-items: center;
|
|
97
|
+
}
|
|
98
|
+
.modal-content {
|
|
99
|
+
padding: 0.5rem 1rem;
|
|
100
|
+
max-height: 50vh;
|
|
101
|
+
overflow: auto;
|
|
102
|
+
}
|
|
103
|
+
.modal-footer {
|
|
104
|
+
text-align: right;
|
|
105
|
+
border-top: 1px solid var(--border);
|
|
106
|
+
padding: 0.5rem 1rem;
|
|
107
|
+
}
|
|
108
|
+
@keyframes fadein {
|
|
109
|
+
from {
|
|
110
|
+
opacity: 0;
|
|
111
|
+
}
|
|
112
|
+
to {
|
|
113
|
+
opacity: 1;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
</style>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script module>
|
|
2
|
+
import { defineMeta } from "@storybook/addon-svelte-csf";
|
|
3
|
+
import ScreenshotTool from "./ScreenshotTool.svelte";
|
|
4
|
+
import BuilderStyleRoot from "../BuilderStyleRoot/BuilderStyleRoot.svelte";
|
|
5
|
+
|
|
6
|
+
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories
|
|
7
|
+
const { Story } = defineMeta({
|
|
8
|
+
title: "Example/ScreenshotTool",
|
|
9
|
+
component: ScreenshotTool,
|
|
10
|
+
tags: ["autodocs"],
|
|
11
|
+
argTypes: {},
|
|
12
|
+
args: {},
|
|
13
|
+
decorators: [() => BuilderStyleRoot],
|
|
14
|
+
});
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<!-- More on writing stories with args: https://storybook.js.org/docs/writing-stories/args -->
|
|
18
|
+
<Story
|
|
19
|
+
name="Primary"
|
|
20
|
+
args={{
|
|
21
|
+
// Prefixes to look for in the pasted document
|
|
22
|
+
prefixes: { Marker: "#mark" },
|
|
23
|
+
// Do something with the markers before screenshotting (e.g. turn off animations)
|
|
24
|
+
parseMarker: (markerString) => "markNewMarkerString",
|
|
25
|
+
}}
|
|
26
|
+
/>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export default ScreenshotTool;
|
|
2
|
+
type ScreenshotTool = SvelteComponent<{
|
|
3
|
+
[x: string]: never;
|
|
4
|
+
}, {
|
|
5
|
+
[evt: string]: CustomEvent<any>;
|
|
6
|
+
}, {}> & {
|
|
7
|
+
$$bindings?: string | undefined;
|
|
8
|
+
};
|
|
9
|
+
declare const ScreenshotTool: $$__sveltets_2_IsomorphicComponent<{
|
|
10
|
+
[x: string]: never;
|
|
11
|
+
}, {
|
|
12
|
+
[evt: string]: CustomEvent<any>;
|
|
13
|
+
}, {}, {}, string>;
|
|
14
|
+
import ScreenshotTool from "./ScreenshotTool.svelte";
|
|
15
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
16
|
+
new (options: import("svelte").ComponentConstructorOptions<Props>): import("svelte").SvelteComponent<Props, Events, Slots> & {
|
|
17
|
+
$$bindings?: Bindings;
|
|
18
|
+
} & Exports;
|
|
19
|
+
(internal: unknown, props: {
|
|
20
|
+
$$events?: Events;
|
|
21
|
+
$$slots?: Slots;
|
|
22
|
+
}): Exports & {
|
|
23
|
+
$set?: any;
|
|
24
|
+
$on?: any;
|
|
25
|
+
};
|
|
26
|
+
z_$$bindings?: Bindings;
|
|
27
|
+
}
|