tosijs-ui 1.0.0

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 (134) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +165 -0
  3. package/dist/ab-test.d.ts +14 -0
  4. package/dist/ab-test.js +116 -0
  5. package/dist/babylon-3d.d.ts +53 -0
  6. package/dist/babylon-3d.js +292 -0
  7. package/dist/bodymovin-player.d.ts +32 -0
  8. package/dist/bodymovin-player.js +172 -0
  9. package/dist/bp-loader.d.ts +1 -0
  10. package/dist/bp-loader.js +26 -0
  11. package/dist/carousel.d.ts +113 -0
  12. package/dist/carousel.js +308 -0
  13. package/dist/code-editor.d.ts +27 -0
  14. package/dist/code-editor.js +102 -0
  15. package/dist/color-input.d.ts +41 -0
  16. package/dist/color-input.js +112 -0
  17. package/dist/data-table.d.ts +79 -0
  18. package/dist/data-table.js +774 -0
  19. package/dist/drag-and-drop.d.ts +2 -0
  20. package/dist/drag-and-drop.js +386 -0
  21. package/dist/editable-rect.d.ts +97 -0
  22. package/dist/editable-rect.js +450 -0
  23. package/dist/filter-builder.d.ts +64 -0
  24. package/dist/filter-builder.js +468 -0
  25. package/dist/float.d.ts +18 -0
  26. package/dist/float.js +170 -0
  27. package/dist/form.d.ts +68 -0
  28. package/dist/form.js +466 -0
  29. package/dist/gamepad.d.ts +34 -0
  30. package/dist/gamepad.js +115 -0
  31. package/dist/icon-data.d.ts +312 -0
  32. package/dist/icon-data.js +308 -0
  33. package/dist/icon-types.d.ts +7 -0
  34. package/dist/icon-types.js +1 -0
  35. package/dist/icons.d.ts +17 -0
  36. package/dist/icons.js +374 -0
  37. package/dist/iife.js +69 -0
  38. package/dist/iife.js.map +49 -0
  39. package/dist/index-iife.d.ts +1 -0
  40. package/dist/index-iife.js +4 -0
  41. package/dist/index.d.ts +37 -0
  42. package/dist/index.js +37 -0
  43. package/dist/index.js.map +47 -0
  44. package/dist/live-example.d.ts +63 -0
  45. package/dist/live-example.js +611 -0
  46. package/dist/localize.d.ts +46 -0
  47. package/dist/localize.js +381 -0
  48. package/dist/make-sorter.d.ts +3 -0
  49. package/dist/make-sorter.js +119 -0
  50. package/dist/make-sorter.test.d.ts +1 -0
  51. package/dist/make-sorter.test.js +48 -0
  52. package/dist/mapbox.d.ts +24 -0
  53. package/dist/mapbox.js +161 -0
  54. package/dist/markdown-viewer.d.ts +17 -0
  55. package/dist/markdown-viewer.js +173 -0
  56. package/dist/match-shortcut.d.ts +9 -0
  57. package/dist/match-shortcut.js +13 -0
  58. package/dist/match-shortcut.test.d.ts +1 -0
  59. package/dist/match-shortcut.test.js +194 -0
  60. package/dist/menu.d.ts +60 -0
  61. package/dist/menu.js +614 -0
  62. package/dist/notifications.d.ts +106 -0
  63. package/dist/notifications.js +308 -0
  64. package/dist/password-strength.d.ts +35 -0
  65. package/dist/password-strength.js +302 -0
  66. package/dist/playwright.config.d.ts +9 -0
  67. package/dist/playwright.config.js +73 -0
  68. package/dist/pop-float.d.ts +10 -0
  69. package/dist/pop-float.js +231 -0
  70. package/dist/rating.d.ts +62 -0
  71. package/dist/rating.js +192 -0
  72. package/dist/rich-text.d.ts +35 -0
  73. package/dist/rich-text.js +296 -0
  74. package/dist/segmented.d.ts +80 -0
  75. package/dist/segmented.js +298 -0
  76. package/dist/select.d.ts +43 -0
  77. package/dist/select.js +427 -0
  78. package/dist/side-nav.d.ts +36 -0
  79. package/dist/side-nav.js +106 -0
  80. package/dist/size-break.d.ts +18 -0
  81. package/dist/size-break.js +118 -0
  82. package/dist/sizer.d.ts +34 -0
  83. package/dist/sizer.js +92 -0
  84. package/dist/src/ab-test.d.ts +14 -0
  85. package/dist/src/babylon-3d.d.ts +53 -0
  86. package/dist/src/bodymovin-player.d.ts +32 -0
  87. package/dist/src/bp-loader.d.ts +0 -0
  88. package/dist/src/carousel.d.ts +113 -0
  89. package/dist/src/code-editor.d.ts +27 -0
  90. package/dist/src/color-input.d.ts +41 -0
  91. package/dist/src/data-table.d.ts +79 -0
  92. package/dist/src/drag-and-drop.d.ts +2 -0
  93. package/dist/src/editable-rect.d.ts +97 -0
  94. package/dist/src/filter-builder.d.ts +64 -0
  95. package/dist/src/float.d.ts +18 -0
  96. package/dist/src/form.d.ts +68 -0
  97. package/dist/src/gamepad.d.ts +34 -0
  98. package/dist/src/icon-data.d.ts +309 -0
  99. package/dist/src/icon-types.d.ts +7 -0
  100. package/dist/src/icons.d.ts +17 -0
  101. package/dist/src/index.d.ts +37 -0
  102. package/dist/src/live-example.d.ts +51 -0
  103. package/dist/src/localize.d.ts +30 -0
  104. package/dist/src/make-sorter.d.ts +3 -0
  105. package/dist/src/mapbox.d.ts +24 -0
  106. package/dist/src/markdown-viewer.d.ts +15 -0
  107. package/dist/src/match-shortcut.d.ts +9 -0
  108. package/dist/src/menu.d.ts +60 -0
  109. package/dist/src/notifications.d.ts +106 -0
  110. package/dist/src/password-strength.d.ts +35 -0
  111. package/dist/src/pop-float.d.ts +10 -0
  112. package/dist/src/rating.d.ts +62 -0
  113. package/dist/src/rich-text.d.ts +28 -0
  114. package/dist/src/segmented.d.ts +80 -0
  115. package/dist/src/select.d.ts +43 -0
  116. package/dist/src/side-nav.d.ts +36 -0
  117. package/dist/src/size-break.d.ts +18 -0
  118. package/dist/src/sizer.d.ts +34 -0
  119. package/dist/src/tab-selector.d.ts +91 -0
  120. package/dist/src/tag-list.d.ts +37 -0
  121. package/dist/src/track-drag.d.ts +5 -0
  122. package/dist/src/version.d.ts +1 -0
  123. package/dist/src/via-tag.d.ts +2 -0
  124. package/dist/tab-selector.d.ts +91 -0
  125. package/dist/tab-selector.js +326 -0
  126. package/dist/tag-list.d.ts +37 -0
  127. package/dist/tag-list.js +375 -0
  128. package/dist/track-drag.d.ts +5 -0
  129. package/dist/track-drag.js +143 -0
  130. package/dist/version.d.ts +1 -0
  131. package/dist/version.js +1 -0
  132. package/dist/via-tag.d.ts +2 -0
  133. package/dist/via-tag.js +102 -0
  134. package/package.json +58 -0
@@ -0,0 +1,63 @@
1
+ import { Component, ElementCreator, PartsMap } from 'tosijs';
2
+ import { TabSelector } from './tab-selector';
3
+ interface ExampleContext {
4
+ [key: string]: any;
5
+ }
6
+ interface ExampleParts extends PartsMap {
7
+ codeEditors: HTMLElement;
8
+ undo: HTMLButtonElement;
9
+ redo: HTMLButtonElement;
10
+ exampleWidgets: HTMLButtonElement;
11
+ editors: TabSelector;
12
+ code: HTMLElement;
13
+ sources: HTMLElement;
14
+ style: HTMLStyleElement;
15
+ example: HTMLElement;
16
+ }
17
+ export declare class LiveExample extends Component<ExampleParts> {
18
+ persistToDom: boolean;
19
+ prettier: boolean;
20
+ prefix: string;
21
+ storageKey: string;
22
+ context: ExampleContext;
23
+ uuid: string;
24
+ remoteId: string;
25
+ lastUpdate: number;
26
+ interval?: any;
27
+ static insertExamples(element: HTMLElement, context?: ExampleContext): void;
28
+ constructor();
29
+ get activeTab(): Element | undefined;
30
+ private getEditorValue;
31
+ private setEditorValue;
32
+ get css(): string;
33
+ set css(code: string);
34
+ get html(): string;
35
+ set html(code: string);
36
+ get js(): string;
37
+ set js(code: string);
38
+ updateUndo: () => void;
39
+ undo: () => void;
40
+ redo: () => void;
41
+ get isMaximized(): boolean;
42
+ flipLayout: () => void;
43
+ exampleMenu: () => void;
44
+ content: () => any[];
45
+ connectedCallback(): void;
46
+ disconnectedCallback(): void;
47
+ copy: () => void;
48
+ toggleMaximize: () => void;
49
+ get remoteKey(): string;
50
+ remoteChange: (event?: StorageEvent) => void;
51
+ showCode: () => void;
52
+ closeCode: () => void;
53
+ openEditorWindow: () => void;
54
+ refreshRemote: () => void;
55
+ updateSources: () => void;
56
+ refresh: () => void;
57
+ initFromElements(elements: HTMLElement[]): void;
58
+ showDefaultTab(): void;
59
+ render(): void;
60
+ }
61
+ export declare const liveExample: ElementCreator<LiveExample>;
62
+ export declare function makeExamplesLive(element: HTMLElement): void;
63
+ export {};
@@ -0,0 +1,611 @@
1
+ /*#
2
+ # example
3
+
4
+ `<xin-example>` makes it easy to insert interactive code examples in a web page. It
5
+ started life as a super lightweight, easier-to-embed implementation of
6
+ [b8rjs's fiddle component](https://b8rjs.com)—which I dearly missed—but now the student
7
+ is, by far, the master. And it's still super lightweight.
8
+
9
+ *You're probably looking at it right now.*
10
+
11
+ ```js
12
+ // this code executes in an async function body
13
+ // it has xinjs, xinjsui, and preview (the preview div) available as local variables
14
+ const { div } = xinjs.elements
15
+ preview.append(div({class: 'example'}, 'fiddle de dee!'))
16
+ preview.append('Try editing some code and hitting refresh…')
17
+ ```
18
+ ```html
19
+ <h2>Example</h2>
20
+ ```
21
+ ```css
22
+ .preview {
23
+ padding: 0 var(--spacing);
24
+ }
25
+
26
+ .example {
27
+ animation: throb ease-in-out 1s infinite alternate;
28
+ }
29
+
30
+ @keyframes throb {
31
+ from { color: blue }
32
+ to { color: red }
33
+ }
34
+ ```
35
+
36
+ You can also create a live-example from HTML. And if you add the `persist-to-dom`
37
+ attribute, it will persist your code to the DOM.
38
+
39
+ <xin-example persist-to-dom>
40
+ <pre class="language-html">
41
+ <h1 class="make-it-red">Pure HTML!</h1>
42
+ <button>Click Me!</button>
43
+ </pre>
44
+ <pre class="language-js">
45
+ preview.querySelector('button').addEventListener('click', () => {
46
+ alert('you clicked?')
47
+ })
48
+ </pre>
49
+ <pre class="language-css">
50
+ .make-it-red {
51
+ color: red;
52
+ }
53
+ </pre>
54
+ </xin-example>
55
+
56
+ You can simply wrap it around a sequence of code blocks in the DOM with the
57
+ languages (js, html, css) as annotations or you can directly set the `js`, `html`,
58
+ and `css` properties.
59
+
60
+ ## Code-Editor
61
+
62
+ The **code-editor** is actually the same component spawned in a new window using
63
+ a couple of clever tricks, the most important of which is leveraging
64
+ [StorageEvent](https://developer.mozilla.org/en-US/docs/Web/API/StorageEvent).
65
+
66
+ This functionality was originally added to make working in XR easier, but it turned
67
+ out that it's just better than the earlier way of doing things.
68
+
69
+ It actually uses just one `localStorage` item to handle any number of code-editors,
70
+ and cleans up after itself when you close the example (including closing stray
71
+ windows.
72
+
73
+ > **To Do** a little refactoring and tweaking to split the the editor off as a
74
+ completely separate component that can be used for other things, and make the
75
+ example itself lighter-weight.
76
+
77
+ ## context
78
+
79
+ A `<xin-example>` can be given a `context` object {[key: string]: any}, which is the
80
+ set of values available in the javascript's execution context (it is wrapped in an
81
+ async function and passed those values). By default, that context comprises `preview`
82
+ (the `<div>` in which the example is rendered), `xinjs` (`* from xinjs`),
83
+ and `xinjsui` (`* from xinjsui`).
84
+
85
+ The `LiveExample` class provides the static `insertExamples(element: HTMLElement)`
86
+ function that will replace any sequence of
87
+ `pre code[class="language-html"],pre code[class="language-js"],pre code[class="language-css"]`
88
+ elements with a `<xin-example>` instance.
89
+ */
90
+ import { Component, elements } from 'xinjs';
91
+ import { codeEditor, CodeEditor } from './code-editor';
92
+ import { tabSelector } from './tab-selector';
93
+ import { icons } from './icons';
94
+ import { popMenu } from './menu';
95
+ const { div, xinSlot, style, button, h4, pre } = elements;
96
+ const AsyncFunction = (async () => {
97
+ /* do not care */
98
+ }).constructor;
99
+ export class LiveExample extends Component {
100
+ persistToDom = false;
101
+ prettier = false;
102
+ prefix = 'lx';
103
+ storageKey = 'live-example-payload';
104
+ context = {};
105
+ uuid = crypto.randomUUID();
106
+ remoteId = '';
107
+ // FIXME workarounds for StorageEvent issue on Quest
108
+ lastUpdate = 0;
109
+ interval;
110
+ static insertExamples(element, context = {}) {
111
+ const sources = [
112
+ ...element.querySelectorAll('.language-html,.language-js,.language-css'),
113
+ ]
114
+ .filter((element) => !element.closest(LiveExample.tagName))
115
+ .map((code) => ({
116
+ block: code.parentElement,
117
+ language: code.classList[0].split('-').pop(),
118
+ code: code.innerText,
119
+ }));
120
+ for (let index = 0; index < sources.length; index += 1) {
121
+ const exampleSources = [sources[index]];
122
+ while (index < sources.length - 1 &&
123
+ sources[index].block.nextElementSibling === sources[index + 1].block) {
124
+ exampleSources.push(sources[index + 1]);
125
+ index += 1;
126
+ }
127
+ const example = liveExample({ context });
128
+ exampleSources[0].block.parentElement.insertBefore(example, exampleSources[0].block);
129
+ exampleSources.forEach((source) => {
130
+ switch (source.language) {
131
+ case 'js':
132
+ example.js = source.code;
133
+ break;
134
+ case 'html':
135
+ example.html = source.code;
136
+ break;
137
+ case 'css':
138
+ example.css = source.code;
139
+ break;
140
+ }
141
+ source.block.remove();
142
+ });
143
+ example.showDefaultTab();
144
+ }
145
+ }
146
+ constructor() {
147
+ super();
148
+ this.initAttributes('persistToDom', 'prettier');
149
+ }
150
+ get activeTab() {
151
+ const { editors } = this.parts;
152
+ return [...editors.children].find((elt) => elt.getAttribute('hidden') === null);
153
+ }
154
+ getEditorValue(which) {
155
+ return this.parts[which].value;
156
+ }
157
+ setEditorValue(which, code) {
158
+ const codeEditor = this.parts[which];
159
+ codeEditor.value = code;
160
+ }
161
+ get css() {
162
+ return this.getEditorValue('css');
163
+ }
164
+ set css(code) {
165
+ this.setEditorValue('css', code);
166
+ }
167
+ get html() {
168
+ return this.getEditorValue('html');
169
+ }
170
+ set html(code) {
171
+ this.setEditorValue('html', code);
172
+ }
173
+ get js() {
174
+ return this.getEditorValue('js');
175
+ }
176
+ set js(code) {
177
+ this.setEditorValue('js', code);
178
+ }
179
+ updateUndo = () => {
180
+ const { activeTab } = this;
181
+ const { undo, redo } = this.parts;
182
+ if (activeTab instanceof CodeEditor && activeTab.editor !== undefined) {
183
+ const undoManager = activeTab.editor.session.getUndoManager();
184
+ undo.disabled = !undoManager.hasUndo();
185
+ redo.disabled = !undoManager.hasRedo();
186
+ }
187
+ else {
188
+ undo.disabled = true;
189
+ redo.disabled = true;
190
+ }
191
+ };
192
+ undo = () => {
193
+ const { activeTab } = this;
194
+ if (activeTab instanceof CodeEditor) {
195
+ activeTab.editor.undo();
196
+ }
197
+ };
198
+ redo = () => {
199
+ const { activeTab } = this;
200
+ if (activeTab instanceof CodeEditor) {
201
+ activeTab.editor.redo();
202
+ }
203
+ };
204
+ get isMaximized() {
205
+ return this.classList.contains('-maximize');
206
+ }
207
+ flipLayout = () => {
208
+ this.classList.toggle('-vertical');
209
+ };
210
+ exampleMenu = () => {
211
+ popMenu({
212
+ target: this.parts.exampleWidgets,
213
+ width: 'auto',
214
+ menuItems: [
215
+ {
216
+ icon: 'edit2',
217
+ caption: 'view/edit code',
218
+ action: this.showCode,
219
+ },
220
+ {
221
+ icon: 'edit',
222
+ caption: 'view/edit code in a new window',
223
+ action: this.openEditorWindow,
224
+ },
225
+ null,
226
+ {
227
+ icon: this.isMaximized ? 'minimize' : 'maximize',
228
+ caption: this.isMaximized ? 'restore preview' : 'maximize preview',
229
+ action: this.toggleMaximize,
230
+ },
231
+ ],
232
+ });
233
+ };
234
+ content = () => [
235
+ div({ part: 'example' }, style({ part: 'style' }), button({
236
+ title: 'example menu',
237
+ part: 'exampleWidgets',
238
+ onClick: this.exampleMenu,
239
+ }, icons.code())),
240
+ div({
241
+ class: 'code-editors',
242
+ part: 'codeEditors',
243
+ hidden: true,
244
+ }, h4('Code'), button({
245
+ title: 'close code',
246
+ class: 'transparent close-button',
247
+ onClick: this.closeCode,
248
+ }, icons.x()), tabSelector({
249
+ part: 'editors',
250
+ onChange: this.updateUndo,
251
+ }, codeEditor({
252
+ name: 'js',
253
+ mode: 'javascript',
254
+ part: 'js',
255
+ }), codeEditor({ name: 'html', mode: 'html', part: 'html' }), codeEditor({ name: 'css', mode: 'css', part: 'css' }), div({
256
+ slot: 'after-tabs',
257
+ class: 'row',
258
+ }, button({
259
+ title: 'undo',
260
+ part: 'undo',
261
+ class: 'transparent',
262
+ onClick: this.undo,
263
+ }, icons.cornerUpLeft()), button({
264
+ title: 'redo',
265
+ part: 'redo',
266
+ class: 'transparent',
267
+ onClick: this.redo,
268
+ }, icons.cornerUpRight()), button({
269
+ title: 'flip direction',
270
+ class: 'transparent',
271
+ onClick: this.flipLayout,
272
+ }, icons.columns({ class: 'layout-indicator' })), button({
273
+ title: 'copy as markdown',
274
+ class: 'transparent',
275
+ onClick: this.copy,
276
+ }, icons.copy()), button({
277
+ title: 'reload',
278
+ class: 'transparent',
279
+ onClick: this.refreshRemote,
280
+ }, icons.refreshCw())))),
281
+ xinSlot({ part: 'sources', hidden: true }),
282
+ ];
283
+ connectedCallback() {
284
+ super.connectedCallback();
285
+ const { sources } = this.parts;
286
+ this.initFromElements([...sources.children]);
287
+ addEventListener('storage', this.remoteChange);
288
+ // FIXME workaround for Quest 3
289
+ this.interval = setInterval(this.remoteChange, 500);
290
+ this.undoInterval = setInterval(this.updateUndo, 250);
291
+ }
292
+ disconnectedCallback() {
293
+ super.disconnectedCallback();
294
+ const { storageKey, remoteKey } = this;
295
+ // FIXME workaround for Quest 3
296
+ clearInterval(this.interval);
297
+ clearInterval(this.undoInterval);
298
+ localStorage.setItem(storageKey, JSON.stringify({
299
+ remoteKey,
300
+ sentAt: Date.now(),
301
+ close: true,
302
+ }));
303
+ }
304
+ copy = () => {
305
+ const js = this.js !== '' ? '```js\n' + this.js.trim() + '\n```\n' : '';
306
+ const html = this.html !== '' ? '```html\n' + this.html.trim() + '\n```\n' : '';
307
+ const css = this.css !== '' ? '```css\n' + this.css.trim() + '\n```\n' : '';
308
+ navigator.clipboard.writeText(js + html + css);
309
+ };
310
+ toggleMaximize = () => {
311
+ this.classList.toggle('-maximize');
312
+ };
313
+ get remoteKey() {
314
+ return this.remoteId !== ''
315
+ ? this.prefix + '-' + this.remoteId
316
+ : this.prefix + '-' + this.uuid;
317
+ }
318
+ remoteChange = (event) => {
319
+ const data = localStorage.getItem(this.storageKey);
320
+ if (event instanceof StorageEvent && event.key !== this.storageKey) {
321
+ return;
322
+ }
323
+ if (data === null) {
324
+ return;
325
+ }
326
+ const { remoteKey, sentAt, css, html, js, close } = JSON.parse(data);
327
+ // FIXME workaround for Quest
328
+ if (sentAt <= this.lastUpdate) {
329
+ return;
330
+ }
331
+ if (remoteKey !== this.remoteKey) {
332
+ return;
333
+ }
334
+ if (close === true) {
335
+ window.close();
336
+ }
337
+ console.log('received new code', sentAt, this.lastUpdate);
338
+ this.lastUpdate = sentAt;
339
+ this.css = css;
340
+ this.html = html;
341
+ this.js = js;
342
+ this.refresh();
343
+ };
344
+ showCode = () => {
345
+ this.classList.add('-maximize');
346
+ this.classList.toggle('-vertical', this.offsetHeight > this.offsetWidth);
347
+ this.parts.codeEditors.hidden = false;
348
+ };
349
+ closeCode = () => {
350
+ if (this.remoteId !== '') {
351
+ window.close();
352
+ }
353
+ else {
354
+ this.classList.remove('-maximize');
355
+ this.parts.codeEditors.hidden = true;
356
+ }
357
+ };
358
+ openEditorWindow = () => {
359
+ const { storageKey, remoteKey, css, html, js, uuid, prefix } = this;
360
+ const href = location.href.split('?')[0] + `?${prefix}=${uuid}`;
361
+ localStorage.setItem(storageKey, JSON.stringify({
362
+ remoteKey,
363
+ sentAt: Date.now(),
364
+ css,
365
+ html,
366
+ js,
367
+ }));
368
+ window.open(href);
369
+ };
370
+ refreshRemote = () => {
371
+ const { remoteKey, css, html, js } = this;
372
+ localStorage.setItem(this.storageKey, JSON.stringify({ remoteKey, sentAt: Date.now(), css, html, js }));
373
+ };
374
+ updateSources = () => {
375
+ if (this.persistToDom) {
376
+ const { sources } = this.parts;
377
+ sources.innerText = '';
378
+ for (const language of ['js', 'css', 'html']) {
379
+ if (this[language]) {
380
+ sources.append(pre({ class: `language-${language}`, innerHTML: this[language] }));
381
+ }
382
+ }
383
+ /*
384
+ let sourceHTML = []
385
+ if (this.html)
386
+ sourceHTML.push(`<pre class="language-html">${this.html}</pre>`)
387
+ if (this.css)
388
+ sourceHTML.push(`<pre class="language-css">${this.css}</pre>`)
389
+ if (this.js) sourceHTML.push(`<pre class="language-js">${this.js}</pre>`)
390
+ sources.innerHTML = sourceHTML.join('\n')
391
+ */
392
+ }
393
+ };
394
+ refresh = () => {
395
+ if (this.remoteId !== '') {
396
+ return;
397
+ }
398
+ const { example, style } = this.parts;
399
+ const preview = div({ class: 'preview' });
400
+ preview.innerHTML = this.html;
401
+ style.innerText = this.css;
402
+ const oldPreview = example.querySelector('.preview');
403
+ if (oldPreview) {
404
+ oldPreview.replaceWith(preview);
405
+ }
406
+ else {
407
+ example.insertBefore(preview, this.parts.exampleWidgets);
408
+ }
409
+ const context = { preview, ...this.context };
410
+ try {
411
+ // @ts-expect-error ts is wrong and it makes me so mad
412
+ const func = new AsyncFunction(...Object.keys(context), this.js);
413
+ func(...Object.values(context)).catch((err) => console.error(err));
414
+ if (this.persistToDom) {
415
+ this.updateSources();
416
+ }
417
+ }
418
+ catch (e) {
419
+ console.error(e);
420
+ window.alert(`Error: ${e}, the console may have more information…`);
421
+ }
422
+ };
423
+ initFromElements(elements) {
424
+ for (const element of elements) {
425
+ element.hidden = true;
426
+ const [mode, ...lines] = element.innerHTML.split('\n');
427
+ if (['js', 'html', 'css'].includes(mode)) {
428
+ const minIndex = lines
429
+ .filter((line) => line.trim() !== '')
430
+ .map((line) => line.match(/^\s*/)[0].length)
431
+ .sort()[0];
432
+ const source = (minIndex > 0 ? lines.map((line) => line.substring(minIndex)) : lines).join('\n');
433
+ this.parts[mode].value = source;
434
+ }
435
+ else {
436
+ const language = ['js', 'html', 'css'].find((language) => element.matches(`.language-${language}`));
437
+ if (language) {
438
+ ;
439
+ this.parts[language].value =
440
+ language === 'html' ? element.innerHTML : element.innerText;
441
+ }
442
+ }
443
+ }
444
+ }
445
+ showDefaultTab() {
446
+ const { editors } = this.parts;
447
+ if (this.js !== '') {
448
+ editors.value = 0;
449
+ }
450
+ else if (this.html !== '') {
451
+ editors.value = 1;
452
+ }
453
+ else if (this.css !== '') {
454
+ editors.value = 2;
455
+ }
456
+ }
457
+ render() {
458
+ super.render();
459
+ if (this.remoteId !== '') {
460
+ const data = localStorage.getItem(this.storageKey);
461
+ if (data !== null) {
462
+ const { remoteKey, sentAt, css, html, js } = JSON.parse(data);
463
+ if (this.remoteKey !== remoteKey) {
464
+ return;
465
+ }
466
+ this.lastUpdate = sentAt;
467
+ this.css = css;
468
+ this.html = html;
469
+ this.js = js;
470
+ this.parts.example.hidden = true;
471
+ this.parts.codeEditors.hidden = false;
472
+ this.classList.add('-maximize');
473
+ this.updateUndo();
474
+ }
475
+ }
476
+ else {
477
+ this.refresh();
478
+ }
479
+ }
480
+ }
481
+ export const liveExample = LiveExample.elementCreator({
482
+ tag: 'xin-example',
483
+ styleSpec: {
484
+ ':host': {
485
+ '--xin-example-height': '320px',
486
+ '--code-editors-bar-bg': '#777',
487
+ '--code-editors-bar-color': '#fff',
488
+ '--widget-bg': '#fff8',
489
+ '--widget-color': '#000',
490
+ position: 'relative',
491
+ display: 'flex',
492
+ height: 'var(--xin-example-height)',
493
+ background: 'var(--background)',
494
+ boxSizing: 'border-box',
495
+ },
496
+ ':host.-maximize': {
497
+ position: 'fixed',
498
+ left: '0',
499
+ top: '0',
500
+ height: '100vh',
501
+ width: '100vw',
502
+ margin: '0 !important',
503
+ },
504
+ '.-maximize': {
505
+ zIndex: 101,
506
+ },
507
+ ':host.-vertical': {
508
+ flexDirection: 'column',
509
+ },
510
+ ':host .layout-indicator': {
511
+ transition: '0.5s ease-out',
512
+ transform: 'rotateZ(270deg)',
513
+ },
514
+ ':host.-vertical .layout-indicator': {
515
+ transform: 'rotateZ(180deg)',
516
+ },
517
+ ':host.-maximize .hide-if-maximized, :host:not(.-maximize) .show-if-maximized': {
518
+ display: 'none',
519
+ },
520
+ ':host [part="example"]': {
521
+ flex: '1 1 50%',
522
+ height: '100%',
523
+ position: 'relative',
524
+ overflowX: 'auto',
525
+ },
526
+ ':host .preview': {
527
+ height: '100%',
528
+ position: 'relative',
529
+ overflow: 'hidden',
530
+ boxShadow: 'inset 0 0 0 2px #8883',
531
+ },
532
+ ':host [part="editors"]': {
533
+ flex: '1 1 200px',
534
+ height: '100%',
535
+ position: 'relative',
536
+ },
537
+ ':host [part="exampleWidgets"]': {
538
+ position: 'absolute',
539
+ left: '5px',
540
+ bottom: '5px',
541
+ '--widget-color': 'var(--brand-color)',
542
+ borderRadius: '5px',
543
+ width: '44px',
544
+ height: '44px',
545
+ lineHeight: '44px',
546
+ zIndex: '100',
547
+ },
548
+ ':host [part="exampleWidgets"] svg': {
549
+ stroke: 'var(--widget-color)',
550
+ },
551
+ ':host .code-editors': {
552
+ overflow: 'hidden',
553
+ background: 'white',
554
+ position: 'relative',
555
+ top: '0',
556
+ right: '0',
557
+ flex: '1 1 50%',
558
+ height: '100%',
559
+ flexDirection: 'column',
560
+ zIndex: '10',
561
+ },
562
+ ':host .code-editors:not([hidden])': {
563
+ display: 'flex',
564
+ },
565
+ ':host .code-editors > h4': {
566
+ padding: '5px',
567
+ margin: '0',
568
+ textAlign: 'center',
569
+ background: 'var(--code-editors-bar-bg)',
570
+ color: 'var(--code-editors-bar-color)',
571
+ cursor: 'move',
572
+ },
573
+ ':host .close-button': {
574
+ position: 'absolute',
575
+ top: '0',
576
+ right: '0',
577
+ color: 'var(--code-editors-bar-color)',
578
+ },
579
+ ':host button.transparent, :host .sizer': {
580
+ width: '32px',
581
+ height: '32px',
582
+ lineHeight: '32px',
583
+ textAlign: 'center',
584
+ padding: '0',
585
+ margin: '0',
586
+ },
587
+ ':host .sizer': {
588
+ cursor: 'nwse-resize',
589
+ },
590
+ },
591
+ });
592
+ export function makeExamplesLive(element) {
593
+ const preElements = [...element.querySelectorAll('pre')].filter((pre) => ['js', 'html', 'css', 'json'].includes(pre.innerText.split('\n')[0]));
594
+ for (let i = 0; i < preElements.length; i++) {
595
+ const parts = [preElements[i]];
596
+ while (preElements[i].nextElementSibling === preElements[i + 1]) {
597
+ parts.push(preElements[i + 1]);
598
+ i += 1;
599
+ }
600
+ const example = liveExample();
601
+ element.insertBefore(example, parts[0]);
602
+ example.initFromElements(parts);
603
+ }
604
+ }
605
+ const params = new URL(window.location.href).searchParams;
606
+ const remoteId = params.get('lx');
607
+ if (remoteId) {
608
+ document.title += ' [code editor]';
609
+ document.body.textContent = '';
610
+ document.body.append(liveExample({ remoteId }));
611
+ }