@potch/html-bin 1.1.2 → 2.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.
- package/CHANGELOG.md +5 -0
- package/LICENSE +21 -0
- package/README.md +28 -1
- package/build/index.min.js +1 -1
- package/build/index.min.js.gz +0 -0
- package/build/index.min.js.map +1 -1
- package/package.json +5 -6
- package/src/index.js +129 -96
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@potch/html-bin",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -21,16 +21,15 @@
|
|
|
21
21
|
"@codemirror/search": "^6.5.11",
|
|
22
22
|
"@codemirror/state": "^6.5.2",
|
|
23
23
|
"@codemirror/view": "^6.36.8",
|
|
24
|
-
"@potch/minifw": "^3.
|
|
25
|
-
"@rollup/plugin-terser": "^0.4.4",
|
|
26
|
-
"@uiw/codemirror-theme-dracula": "^4.23.12",
|
|
27
|
-
"rollup-plugin-import-css": "^4.0.1"
|
|
24
|
+
"@potch/minifw": "^3.2.1"
|
|
28
25
|
},
|
|
29
26
|
"devDependencies": {
|
|
30
27
|
"@potch/tinyserve": "^1.5.1",
|
|
31
28
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
|
29
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
32
30
|
"cssnano": "^7.0.7",
|
|
33
31
|
"postcss": "^8.5.4",
|
|
34
|
-
"rollup": "^4.40.2"
|
|
32
|
+
"rollup": "^4.40.2",
|
|
33
|
+
"rollup-plugin-import-css": "^4.0.1"
|
|
35
34
|
}
|
|
36
35
|
}
|
package/src/index.js
CHANGED
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
highlightSpecialChars,
|
|
5
5
|
drawSelection,
|
|
6
6
|
dropCursor,
|
|
7
|
-
crosshairCursor,
|
|
8
7
|
highlightActiveLine,
|
|
9
8
|
keymap,
|
|
10
9
|
EditorView,
|
|
@@ -16,8 +15,11 @@ import {
|
|
|
16
15
|
bracketMatching,
|
|
17
16
|
} from "@codemirror/language";
|
|
18
17
|
import { history, defaultKeymap, historyKeymap } from "@codemirror/commands";
|
|
19
|
-
import {
|
|
18
|
+
import { javascript } from "@codemirror/lang-javascript";
|
|
19
|
+
import { html } from "@codemirror/lang-html";
|
|
20
|
+
import { css } from "@codemirror/lang-css";
|
|
20
21
|
|
|
22
|
+
// CodeMirror config for tab editors
|
|
21
23
|
const cmSetup = [
|
|
22
24
|
lineNumbers(),
|
|
23
25
|
highlightActiveLineGutter(),
|
|
@@ -28,9 +30,7 @@ const cmSetup = [
|
|
|
28
30
|
EditorState.allowMultipleSelections.of(true),
|
|
29
31
|
indentOnInput(),
|
|
30
32
|
bracketMatching(),
|
|
31
|
-
crosshairCursor(),
|
|
32
33
|
highlightActiveLine(),
|
|
33
|
-
highlightSelectionMatches(),
|
|
34
34
|
EditorView.theme(
|
|
35
35
|
{
|
|
36
36
|
".cm-content": {
|
|
@@ -39,13 +39,9 @@ const cmSetup = [
|
|
|
39
39
|
},
|
|
40
40
|
{ dark: true }
|
|
41
41
|
),
|
|
42
|
-
keymap.of([...defaultKeymap, ...
|
|
42
|
+
keymap.of([...defaultKeymap, ...historyKeymap]),
|
|
43
43
|
];
|
|
44
44
|
|
|
45
|
-
import { javascript } from "@codemirror/lang-javascript";
|
|
46
|
-
import { html } from "@codemirror/lang-html";
|
|
47
|
-
import { css } from "@codemirror/lang-css";
|
|
48
|
-
|
|
49
45
|
import { classHighlighter } from "@lezer/highlight";
|
|
50
46
|
import {
|
|
51
47
|
dom as _,
|
|
@@ -53,6 +49,7 @@ import {
|
|
|
53
49
|
computed,
|
|
54
50
|
effect as _effect,
|
|
55
51
|
on as _on,
|
|
52
|
+
onEffect,
|
|
56
53
|
} from "@potch/minifw";
|
|
57
54
|
|
|
58
55
|
import styles from "./style.css";
|
|
@@ -79,6 +76,61 @@ const debounceComputed = (s, ms) => {
|
|
|
79
76
|
|
|
80
77
|
const maybe = (condition, value) => (condition ? value : "");
|
|
81
78
|
|
|
79
|
+
const createEditors = (sources, parents, updateFactory) => {
|
|
80
|
+
return {
|
|
81
|
+
js: new EditorView({
|
|
82
|
+
doc: sources.js.value,
|
|
83
|
+
parent: parents.js,
|
|
84
|
+
extensions: [
|
|
85
|
+
cmSetup,
|
|
86
|
+
javascript(),
|
|
87
|
+
syntaxHighlighting(classHighlighter),
|
|
88
|
+
EditorView.updateListener.of(updateFactory(sources.js)),
|
|
89
|
+
],
|
|
90
|
+
}),
|
|
91
|
+
css: new EditorView({
|
|
92
|
+
doc: sources.css.value,
|
|
93
|
+
parent: parents.css,
|
|
94
|
+
extensions: [
|
|
95
|
+
cmSetup,
|
|
96
|
+
css(),
|
|
97
|
+
syntaxHighlighting(classHighlighter),
|
|
98
|
+
EditorView.updateListener.of(updateFactory(sources.css)),
|
|
99
|
+
],
|
|
100
|
+
}),
|
|
101
|
+
html: new EditorView({
|
|
102
|
+
doc: sources.html.value,
|
|
103
|
+
parent: parents.html,
|
|
104
|
+
extensions: [
|
|
105
|
+
cmSetup,
|
|
106
|
+
html(),
|
|
107
|
+
syntaxHighlighting(classHighlighter),
|
|
108
|
+
EditorView.updateListener.of(updateFactory(sources.html)),
|
|
109
|
+
],
|
|
110
|
+
}),
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/*
|
|
115
|
+
create a bin component.
|
|
116
|
+
Options:
|
|
117
|
+
- `container` (optional Element): will automatically append the bin if provided
|
|
118
|
+
- `sources`: object with optional `{ js, css, html }` strings of source to put
|
|
119
|
+
in the corresponding editors
|
|
120
|
+
- `split`: number from [0, 1] specifying the ratio between the editor and
|
|
121
|
+
preview panes
|
|
122
|
+
- `width`: CSS string to override default `--bin-width` value
|
|
123
|
+
- `height`: CSS string to override default `--bin-height` value
|
|
124
|
+
Returns object with the following fields:
|
|
125
|
+
- `el`: Element of the outermost HTML element of the bin
|
|
126
|
+
- `editors`: contains `{ js, css, html }` properties with the CodeMirror
|
|
127
|
+
instances for each editor tab
|
|
128
|
+
- `activeTab`: a signal representing the currently active editor tab
|
|
129
|
+
- `start`: call this function if you did not provide the `container` option
|
|
130
|
+
after attaching `el` to the DOM
|
|
131
|
+
- `teardown`: call this if you are removing and destroying the bin to
|
|
132
|
+
disconnect all listeners
|
|
133
|
+
*/
|
|
82
134
|
export const createBin = ({
|
|
83
135
|
container,
|
|
84
136
|
sources: rawSources,
|
|
@@ -94,6 +146,8 @@ export const createBin = ({
|
|
|
94
146
|
const editorSplit = signal(parseFloat(split) || 0.5);
|
|
95
147
|
|
|
96
148
|
const teardownFns = [];
|
|
149
|
+
const stopCapturingEffects = onEffect((fn) => teardownFns.push(fn));
|
|
150
|
+
|
|
97
151
|
const effect = (...args) => {
|
|
98
152
|
teardownFns.push(_effect(...args));
|
|
99
153
|
};
|
|
@@ -150,17 +204,6 @@ export const createBin = ({
|
|
|
150
204
|
</head>
|
|
151
205
|
<body>
|
|
152
206
|
${sources.html.value}
|
|
153
|
-
<script>
|
|
154
|
-
window.onerror = function (msg, file, line, col, error) {
|
|
155
|
-
window.top.postMessage({
|
|
156
|
-
type: 'error',
|
|
157
|
-
msg: msg,
|
|
158
|
-
line: line,
|
|
159
|
-
col: col,
|
|
160
|
-
stack: error.stack.split('\\n')
|
|
161
|
-
}, '*');
|
|
162
|
-
};
|
|
163
|
-
</script>
|
|
164
207
|
<script type="module" src="${scriptURL.value}"></script>
|
|
165
208
|
</body>
|
|
166
209
|
</html>
|
|
@@ -180,27 +223,29 @@ export const createBin = ({
|
|
|
180
223
|
|
|
181
224
|
// editor tabs
|
|
182
225
|
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
(
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
226
|
+
const editorPanes = {
|
|
227
|
+
js: _("div", {
|
|
228
|
+
className: computed(
|
|
229
|
+
() =>
|
|
230
|
+
"bin__editor bin__editor--js" +
|
|
231
|
+
maybe(activeTab.value === "js", " bin__editor--active")
|
|
232
|
+
),
|
|
233
|
+
}),
|
|
234
|
+
css: _("div", {
|
|
235
|
+
className: computed(
|
|
236
|
+
() =>
|
|
237
|
+
"bin__editor bin__editor--css" +
|
|
238
|
+
maybe(activeTab.value === "css", " bin__editor--active")
|
|
239
|
+
),
|
|
240
|
+
}),
|
|
241
|
+
html: _("div", {
|
|
242
|
+
className: computed(
|
|
243
|
+
() =>
|
|
244
|
+
"bin__editor bin__editor--html" +
|
|
245
|
+
maybe(activeTab.value === "html", " bin__editor--active")
|
|
246
|
+
),
|
|
247
|
+
}),
|
|
248
|
+
};
|
|
204
249
|
|
|
205
250
|
const tabsForm = _(
|
|
206
251
|
"form",
|
|
@@ -212,19 +257,34 @@ export const createBin = ({
|
|
|
212
257
|
_(
|
|
213
258
|
"label",
|
|
214
259
|
{ className: "bin__tab bin__editor_tab" },
|
|
215
|
-
_("input", {
|
|
260
|
+
_("input", {
|
|
261
|
+
type: "radio",
|
|
262
|
+
name: "tab",
|
|
263
|
+
value: "html",
|
|
264
|
+
checked: computed(() => activeTab.value === "html"),
|
|
265
|
+
}),
|
|
216
266
|
"html"
|
|
217
267
|
),
|
|
218
268
|
_(
|
|
219
269
|
"label",
|
|
220
270
|
{ className: "bin__tab bin__editor_tab" },
|
|
221
|
-
_("input", {
|
|
271
|
+
_("input", {
|
|
272
|
+
type: "radio",
|
|
273
|
+
name: "tab",
|
|
274
|
+
value: "css",
|
|
275
|
+
checked: computed(() => activeTab.value === "css"),
|
|
276
|
+
}),
|
|
222
277
|
"css"
|
|
223
278
|
),
|
|
224
279
|
_(
|
|
225
280
|
"label",
|
|
226
281
|
{ className: "bin__tab bin__editor_tab" },
|
|
227
|
-
_("input", {
|
|
282
|
+
_("input", {
|
|
283
|
+
type: "radio",
|
|
284
|
+
name: "tab",
|
|
285
|
+
value: "js",
|
|
286
|
+
checked: computed(() => activeTab.value === "js"),
|
|
287
|
+
}),
|
|
228
288
|
"javascript"
|
|
229
289
|
),
|
|
230
290
|
_(
|
|
@@ -234,6 +294,7 @@ export const createBin = ({
|
|
|
234
294
|
type: "radio",
|
|
235
295
|
name: "tab",
|
|
236
296
|
value: "preview",
|
|
297
|
+
checked: computed(() => activeTab.value === "preview"),
|
|
237
298
|
disabled: computed(() => (isMiniMode.value ? false : true)),
|
|
238
299
|
}),
|
|
239
300
|
"preview",
|
|
@@ -243,9 +304,7 @@ export const createBin = ({
|
|
|
243
304
|
className: "bin__preview__refresh bin__iconbutton",
|
|
244
305
|
title: "reload the preview",
|
|
245
306
|
"aria-label": "reload the preview",
|
|
246
|
-
onclick: () =>
|
|
247
|
-
previewEl.value?.contentWindow.location.reload();
|
|
248
|
-
},
|
|
307
|
+
onclick: () => reloadPreview(),
|
|
249
308
|
},
|
|
250
309
|
"🔁"
|
|
251
310
|
)
|
|
@@ -258,6 +317,12 @@ export const createBin = ({
|
|
|
258
317
|
|
|
259
318
|
let previewEl = signal();
|
|
260
319
|
|
|
320
|
+
const reloadPreview = () => {
|
|
321
|
+
if (previewEl.value) {
|
|
322
|
+
previewEl.value.src = previewURL.value;
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
|
|
261
326
|
const binEl = _(
|
|
262
327
|
"div",
|
|
263
328
|
{
|
|
@@ -298,9 +363,7 @@ export const createBin = ({
|
|
|
298
363
|
className: "bin__preview__refresh bin__iconbutton",
|
|
299
364
|
title: "reload the preview",
|
|
300
365
|
"aria-label": "reload the preview",
|
|
301
|
-
onclick: () =>
|
|
302
|
-
previewEl.value?.contentWindow.location.reload();
|
|
303
|
-
},
|
|
366
|
+
onclick: () => reloadPreview(),
|
|
304
367
|
},
|
|
305
368
|
"🔁"
|
|
306
369
|
)
|
|
@@ -318,9 +381,9 @@ export const createBin = ({
|
|
|
318
381
|
},
|
|
319
382
|
})
|
|
320
383
|
),
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
384
|
+
editorPanes.js,
|
|
385
|
+
editorPanes.css,
|
|
386
|
+
editorPanes.html,
|
|
324
387
|
resizerEl,
|
|
325
388
|
_("iframe", {
|
|
326
389
|
ref: previewEl,
|
|
@@ -338,7 +401,7 @@ export const createBin = ({
|
|
|
338
401
|
if (width) binEl.style.setProperty("--bin-width", width);
|
|
339
402
|
if (height) binEl.style.setProperty("--bin-height", height);
|
|
340
403
|
|
|
341
|
-
// resizing
|
|
404
|
+
// split resizing
|
|
342
405
|
|
|
343
406
|
const ilerp = (a, b, i) => (i - a) / (b - a);
|
|
344
407
|
const clamp = (a, b, n) => Math.max(a, Math.min(n, b));
|
|
@@ -373,6 +436,7 @@ export const createBin = ({
|
|
|
373
436
|
updateResize(e);
|
|
374
437
|
});
|
|
375
438
|
|
|
439
|
+
// set the split in CSS, factoring in expanded panes
|
|
376
440
|
effect(() => {
|
|
377
441
|
binEl.style.setProperty(
|
|
378
442
|
"--resizer-split",
|
|
@@ -393,58 +457,25 @@ export const createBin = ({
|
|
|
393
457
|
|
|
394
458
|
// create CM editors
|
|
395
459
|
|
|
396
|
-
const
|
|
460
|
+
const updateFactory = (s) => (v) => {
|
|
397
461
|
if (v.docChanged) {
|
|
398
462
|
// how to get the full text content from the editor
|
|
399
463
|
s.value = v.state.doc.toString();
|
|
400
464
|
}
|
|
401
465
|
};
|
|
402
466
|
|
|
403
|
-
const editors =
|
|
404
|
-
js: new EditorView({
|
|
405
|
-
doc: sources.js.value,
|
|
406
|
-
parent: jsEditorEl,
|
|
407
|
-
extensions: [
|
|
408
|
-
cmSetup,
|
|
409
|
-
javascript(),
|
|
410
|
-
syntaxHighlighting(classHighlighter),
|
|
411
|
-
EditorView.updateListener.of(update(sources.js)),
|
|
412
|
-
],
|
|
413
|
-
}),
|
|
414
|
-
css: new EditorView({
|
|
415
|
-
doc: sources.css.value,
|
|
416
|
-
parent: cssEditorEl,
|
|
417
|
-
extensions: [
|
|
418
|
-
cmSetup,
|
|
419
|
-
css(),
|
|
420
|
-
syntaxHighlighting(classHighlighter),
|
|
421
|
-
EditorView.updateListener.of(update(sources.css)),
|
|
422
|
-
],
|
|
423
|
-
}),
|
|
424
|
-
html: new EditorView({
|
|
425
|
-
doc: sources.html.value,
|
|
426
|
-
parent: htmlEditorEl,
|
|
427
|
-
extensions: [
|
|
428
|
-
cmSetup,
|
|
429
|
-
html(),
|
|
430
|
-
syntaxHighlighting(classHighlighter),
|
|
431
|
-
EditorView.updateListener.of(update(sources.html)),
|
|
432
|
-
],
|
|
433
|
-
}),
|
|
434
|
-
};
|
|
467
|
+
const editors = createEditors(sources, editorPanes, updateFactory);
|
|
435
468
|
|
|
436
469
|
// mount bin and start
|
|
437
|
-
|
|
438
|
-
|
|
470
|
+
const start = () => widthObserver.observe(binEl);
|
|
471
|
+
if (container) {
|
|
472
|
+
container.append(binEl);
|
|
473
|
+
start();
|
|
474
|
+
}
|
|
439
475
|
|
|
440
476
|
// destroy / teardown
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
teardownFns.push(...node._effects);
|
|
444
|
-
}
|
|
445
|
-
for (let n of node?.childNodes) findEffects(n);
|
|
446
|
-
};
|
|
447
|
-
findEffects(binEl);
|
|
477
|
+
stopCapturingEffects();
|
|
478
|
+
console.log(teardownFns);
|
|
448
479
|
|
|
449
480
|
const teardown = () => {
|
|
450
481
|
while (teardownFns.length) {
|
|
@@ -454,8 +485,10 @@ export const createBin = ({
|
|
|
454
485
|
};
|
|
455
486
|
|
|
456
487
|
return {
|
|
488
|
+
el: binEl,
|
|
457
489
|
editors,
|
|
458
490
|
activeTab,
|
|
491
|
+
start,
|
|
459
492
|
teardown,
|
|
460
493
|
};
|
|
461
494
|
};
|