@potch/html-bin 1.1.3 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@potch/html-bin",
3
- "version": "1.1.3",
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.1.1",
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 { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
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, ...searchKeymap, ...historyKeymap]),
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 jsEditorEl = _("div", {
184
- className: computed(
185
- () =>
186
- "bin__editor bin__editor--js" +
187
- maybe(activeTab.value === "js", " bin__editor--active")
188
- ),
189
- });
190
- const cssEditorEl = _("div", {
191
- className: computed(
192
- () =>
193
- "bin__editor bin__editor--css" +
194
- maybe(activeTab.value === "css", " bin__editor--active")
195
- ),
196
- });
197
- const htmlEditorEl = _("div", {
198
- className: computed(
199
- () =>
200
- "bin__editor bin__editor--html" +
201
- maybe(activeTab.value === "html", " bin__editor--active")
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", { type: "radio", name: "tab", value: "html" }),
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", { type: "radio", name: "tab", value: "css" }),
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", { type: "radio", name: "tab", value: "js", checked: true }),
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
- jsEditorEl,
322
- cssEditorEl,
323
- htmlEditorEl,
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,45 +457,14 @@ export const createBin = ({
393
457
 
394
458
  // create CM editors
395
459
 
396
- const update = (s) => (v) => {
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
470
  const start = () => widthObserver.observe(binEl);
@@ -441,13 +474,8 @@ export const createBin = ({
441
474
  }
442
475
 
443
476
  // destroy / teardown
444
- const findEffects = (node) => {
445
- if (node._effects) {
446
- teardownFns.push(...node._effects);
447
- }
448
- for (let n of node?.childNodes) findEffects(n);
449
- };
450
- findEffects(binEl);
477
+ stopCapturingEffects();
478
+ console.log(teardownFns);
451
479
 
452
480
  const teardown = () => {
453
481
  while (teardownFns.length) {