subay 0.0.12 → 0.1.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/src/h.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  // deno-lint-ignore-file no-explicit-any no-sloppy-imports
2
+ import { arr } from './arr.js';
2
3
  import { cleanup, isReactive, root, S, sample } from './s.js';
3
4
 
4
5
  const enum InterNodeType {
@@ -28,88 +29,61 @@ type InterAttributeNode = {
28
29
  };
29
30
 
30
31
  const cache = new Map<TemplateStringsArray, InterTreeNode[]>();
31
- function random6LetterString() {
32
- const BASE = 36;
33
- const power5 = BASE ** 5;
34
- const min = 10 * power5;
35
- const length = 26 * power5;
36
- return Math.floor(Math.random() * length + min).toString(BASE);
37
- }
38
- const MarkStart = random6LetterString();
39
- const MarkEnd = random6LetterString();
32
+ const rnds = () => String.fromCharCode((97 + Math.random() * 26) | 0) + Math.random().toString(36).substring(2, 5);
33
+ const MarkStart = rnds();
34
+ const MarkEnd = rnds();
40
35
  const MarkReg = new RegExp(`([\\s\\S]*?)${MarkStart}(\\d+)${MarkEnd}([\\s\\S]*?)(?=${MarkStart}\\d+${MarkEnd}|$)`, 'g');
41
36
  const BlankText = /\s*\n\s*/g;
42
37
  const domParser = new DOMParser();
38
+ const isNum = (d: any): d is number => typeof d === 'number';
39
+ const isFun = (d: any): d is (...args: any[]) => any => typeof d === 'function';
40
+ const isStr = (d: any): d is string => typeof d === 'string';
41
+ const deferRemove = <T extends Element | Comment | Text>(d: T) => (cleanup(() => d.remove()), d);
42
+ const callif = <T>(fun: (() => T) | T): T => (isFun(fun) ? fun() : fun);
43
43
 
44
44
  const parseText = (data: string) => {
45
- const result: Array<string | number> = [];
46
45
  data = data.replace(BlankText, '');
46
+ if (!data) return [];
47
47
  if (data.includes(MarkStart) && data.includes(MarkEnd)) {
48
+ const result: Array<string | number> = [];
48
49
  for (const res of data.matchAll(MarkReg)) {
49
50
  const [, prefix, index, suffix] = res;
50
51
  if (prefix) {
51
52
  result.push(prefix);
52
53
  }
53
54
  if (index) {
54
- result.push(Number(index));
55
+ result.push(+index);
55
56
  }
56
57
  if (suffix) {
57
58
  result.push(suffix);
58
59
  }
59
60
  }
60
- } else if (data) {
61
- result.push(data);
61
+ return result;
62
62
  }
63
- return result;
63
+ return [data];
64
64
  };
65
65
 
66
66
  const unparseText = (data: Array<string | number | boolean>, values: any[]) => {
67
67
  if (data.length === 1) {
68
- if (typeof data[0] === 'number') {
69
- return values[data[0]!];
70
- }
71
- return data[0];
68
+ const head = data[0];
69
+ return isNum(head) ? values[head] : head;
72
70
  }
73
- return S(() => {
74
- return data
75
- .map((it) => {
76
- if (typeof it === 'number') {
77
- const value = values[it];
78
- if (typeof value === 'function') {
79
- return value();
80
- }
81
- return value;
82
- }
83
- return it;
84
- })
85
- .join('');
86
- });
71
+ return S(() => data.map((it) => (isNum(it) ? callif(values[it]) : it)).join(''));
87
72
  };
88
73
 
89
74
  const parse = (
90
- docType: DOMParserSupportedType,
91
- wrapSource: (source: string) => string,
92
- unwrapNode: (node: Document) => NodeList,
75
+ markup2nodeList: (markup: string) => NodeList,
93
76
  segments: TemplateStringsArray,
94
77
  indexMap: number[],
95
78
  ): InterTreeNode[] => {
96
79
  if (cache.has(segments)) {
97
80
  return cache.get(segments)!;
98
81
  }
99
-
100
82
  const htmlStr = segments
101
- .flatMap((segment, index, { length }) => {
102
- if (index === length - 1) {
103
- return [segment];
104
- }
105
- return [segment, MarkStart, indexMap[index], MarkEnd];
106
- })
83
+ .flatMap((segment, index, { length }) =>
84
+ index === length - 1 ? [segment] : [segment, MarkStart, indexMap[index], MarkEnd],
85
+ )
107
86
  .join('');
108
-
109
- const wrappedHtmlStr = wrapSource(htmlStr);
110
-
111
- const document = domParser.parseFromString(wrappedHtmlStr, docType);
112
-
113
87
  const dom2treenode = (domNodeList: NodeList) => {
114
88
  const nodeList: InterTreeNode[] = [];
115
89
  for (
@@ -117,115 +91,79 @@ const parse = (
117
91
  item != null;
118
92
  item = nextSibling, nextSibling = nextSibling?.nextSibling, index++
119
93
  ) {
120
- do
121
- remove_and_dec_index: {
122
- if (item.nodeType === 1 /* Node.ELEMENT_NODE*/) {
123
- const el = item as Element;
124
- const tag = parseText(el.tagName.toLowerCase());
125
- const isVariableElem = tag.length > 1 || typeof tag[0] === 'number';
126
-
127
- nodeList.push({
128
- _ns: el.namespaceURI,
129
- _type: InterNodeType.Element,
130
- _tag: tag,
131
- _attrs: Array.from(el.attributes).flatMap((attr) => {
132
- const name = parseText(attr.nodeName);
133
- const value = parseText(attr.value);
134
- const isVariableAttr =
135
- name.length > 1 ||
136
- typeof name[0] === 'number' ||
137
- value.length > 1 ||
138
- typeof value[0] === 'number';
139
-
140
- if (!isVariableElem && !isVariableAttr) {
141
- return [];
142
- } else {
143
- el.removeAttribute(attr.nodeName);
144
- }
145
- return [
146
- {
147
- _name: name,
148
- _value: value.length === 0 ? [true] : value,
149
- } satisfies InterAttributeNode,
150
- ];
151
- }),
152
- _content: el.childNodes.length > 0 ? dom2treenode(el.childNodes) : [],
153
- _sourceNode: isVariableElem ? null : el,
154
- _sourceIndex: index,
155
- });
156
-
157
- if (!isVariableElem) {
158
- break remove_and_dec_index;
94
+ if (item.nodeType === 1 /* Node.ELEMENT_NODE*/) {
95
+ const el = item as Element;
96
+ const tag = parseText(el.tagName.toLowerCase());
97
+ const isVariableElem = tag.length > 1 || isNum(tag[0]);
98
+
99
+ nodeList.push({
100
+ _ns: el.namespaceURI,
101
+ _type: InterNodeType.Element,
102
+ _tag: tag,
103
+ _attrs: Array.from(el.attributes).flatMap((attr) => {
104
+ const name = parseText(attr.nodeName);
105
+ const value = parseText(attr.value);
106
+ const isVariableAttr = name.length > 1 || isNum(name[0]) || value.length > 1 || isNum(value[0]);
107
+
108
+ if (!isVariableElem && !isVariableAttr) {
109
+ return [];
159
110
  }
160
- } else if (item.nodeType === 3 /*Node.TEXT_NODE*/) {
161
- const data = (item as Text).data;
162
- const result = parseText(data);
163
- if (result.length > 0) {
164
- nodeList.push({
165
- _type: InterNodeType.Text,
166
- _data: result,
167
- });
168
- }
169
- }
170
- (item as Comment | Text | Element).remove();
171
- index--;
111
+ el.removeAttribute(attr.nodeName);
112
+ return [
113
+ {
114
+ _name: name,
115
+ _value: value.length === 0 ? [true] : value,
116
+ } satisfies InterAttributeNode,
117
+ ];
118
+ }),
119
+ _content: el.childNodes.length > 0 ? dom2treenode(el.childNodes) : [],
120
+ _sourceNode: isVariableElem ? null : el,
121
+ _sourceIndex: index,
122
+ });
123
+
124
+ if (!isVariableElem) {
125
+ continue;
172
126
  }
173
- while (false);
127
+ } else if (item.nodeType === 3 /*Node.TEXT_NODE*/) {
128
+ const result = parseText((item as Text).data);
129
+ if (result.length > 0) {
130
+ nodeList.push({
131
+ _type: InterNodeType.Text,
132
+ _data: result,
133
+ });
134
+ }
135
+ }
136
+ (item as Comment | Text | Element).remove();
137
+ index--;
174
138
  }
175
139
  return nodeList;
176
140
  };
177
-
178
- const rootList = unwrapNode(document);
179
-
180
- const treeNode = dom2treenode(rootList);
181
-
141
+ const treeNode = dom2treenode(markup2nodeList(htmlStr));
182
142
  cache.set(segments, treeNode);
183
-
184
143
  return treeNode;
185
144
  };
186
145
 
187
146
  const element = (
188
147
  ns: string | null,
189
148
  sourceNode: Element | null,
190
- tagName: string | (() => string) | ((...props: any[]) => Node[]),
191
- attrs: InterAttributeNode[],
192
- values: any[],
193
- child: () => Node[],
149
+ tagName: string | (() => string),
150
+ child: (el: Element) => Node[],
194
151
  ): Node[] => {
195
- if (typeof tagName === 'function') {
196
- if (isReactive(tagName)) {
197
- return fragment(() => {
198
- const realTagName = tagName();
199
- return element(ns, null, realTagName, attrs, values, child);
200
- });
201
- } else {
202
- return fragment(() => {
203
- return tagName(
204
- ...attrs.flatMap(({ _name: name, _value: value }) => {
205
- if (name[0] === '...' && typeof name[1] === 'number') {
206
- return unparseText(name.slice(1), values);
207
- } else {
208
- return unparseText(value, values);
209
- }
210
- }),
211
- child,
212
- ) as Node[];
213
- });
214
- }
152
+ if (isFun(tagName)) {
153
+ return fragment(() => {
154
+ const realTagName = tagName();
155
+ if (!isStr(realTagName)) {
156
+ throw new Error(`Expecting string returned from ${tagName} but get ${realTagName}`, { cause: tagName });
157
+ }
158
+ return element(ns, null, realTagName, child);
159
+ });
215
160
  }
216
- if (typeof tagName !== 'string') {
161
+ if (!isStr(tagName)) {
217
162
  return [];
218
163
  }
219
- const el = sourceNode || (ns ? document.createElementNS(ns, tagName) : document.createElement(tagName));
164
+ const el = deferRemove(sourceNode || (ns ? document.createElementNS(ns, tagName) : document.createElement(tagName)));
220
165
  S(() => {
221
- for (const { _name: name, _value: value } of attrs) {
222
- if (name[0] === '...' && typeof name[1] === 'number') {
223
- spreadAttrs(el, unparseText(name.slice(1), values));
224
- } else {
225
- attribute(el, unparseText(name, values), unparseText(value, values));
226
- }
227
- }
228
- const subnodes = child();
166
+ const subnodes = child(el);
229
167
  if (subnodes.length === 0) {
230
168
  return;
231
169
  }
@@ -242,28 +180,17 @@ const element = (
242
180
  }
243
181
  }
244
182
  });
245
- cleanup(() => el.remove());
246
183
  return [el];
247
184
  };
248
185
  let fragmentHeadNode: null | Comment = null;
249
186
  const fragment = (child: () => Node[]): Node[] => {
250
- const firstNode = new Comment('');
251
- cleanup(() => firstNode.remove());
187
+ const firstNode = deferRemove(new Text(''));
252
188
  const tempArray: Node[] = [firstNode];
253
189
  S(() => {
254
190
  fragmentHeadNode = firstNode;
255
191
  const c = child();
256
192
  const carr = Array.isArray(c) ? c : [c];
257
-
258
- const nodified = carr.map((c) => {
259
- if (c instanceof Node) {
260
- return c;
261
- }
262
- const node = new Text(c);
263
- cleanup(() => node.remove());
264
- return node;
265
- });
266
-
193
+ const nodified = carr.map((c) => (c instanceof Node ? c : deferRemove(new Text(c))));
267
194
  if (firstNode.isConnected) {
268
195
  firstNode.after(...nodified);
269
196
  } else {
@@ -273,218 +200,82 @@ const fragment = (child: () => Node[]): Node[] => {
273
200
 
274
201
  return tempArray;
275
202
  };
276
- function arrTrans<T>(a: readonly T[], b: readonly T[], a2b: (a: T) => void, dela: (a: T) => void, keep: (a: T) => void): void {
277
- const aLen = a.length;
278
- const bLen = b.length;
279
-
280
- if (a === b || (aLen === 0 && bLen === 0)) {
281
- return;
282
- }
283
-
284
- let start = 0;
285
- const minLen = Math.min(aLen, bLen);
286
- while (start < minLen && a[start] === b[start]) {
287
- start++;
288
- }
289
-
290
- let end = 0;
291
- while (end < minLen - start && a[aLen - 1 - end] === b[bLen - 1 - end]) {
292
- end++;
293
- }
294
-
295
- const aMidLen = aLen - start - end;
296
- const bMidLen = bLen - start - end;
297
-
298
- if (aMidLen === 0 && bMidLen === 0) {
299
- for (let i = 0; i < start; i++) {
300
- keep(a[i]!);
301
- }
302
- for (let i = 0; i < end; i++) {
303
- keep(a[aLen - end + i]!);
304
- }
305
- return;
306
- }
307
-
308
- const bIndexMap = new Map<T, number>();
309
- for (let i = 0; i < bMidLen; i++) {
310
- bIndexMap.set(b[start + i]!, i);
311
- }
312
-
313
- const indices: number[] = [];
314
- for (let i = 0; i < aMidLen; i++) {
315
- const val = a[start + i]!;
316
- const bIdx = bIndexMap.get(val);
317
- if (bIdx !== undefined) {
318
- indices.push(bIdx);
319
- }
320
- }
321
-
322
- const tails: number[] = [];
323
- const parents: number[] = new Array(indices.length).fill(-1);
324
- const tailIndices: number[] = [];
325
-
326
- for (let i = 0; i < indices.length; i++) {
327
- const idx = indices[i]!;
328
-
329
- let left = 0;
330
- let right = tails.length;
331
- while (left < right) {
332
- const mid = (left + right) >> 1;
333
- if (tails[mid]! < idx) {
334
- left = mid + 1;
335
- } else {
336
- right = mid;
337
- }
338
- }
339
-
340
- const pos = left;
341
- if (pos === tails.length) {
342
- tails.push(idx);
343
- tailIndices.push(i);
344
- } else {
345
- tails[pos] = idx;
346
- tailIndices[pos] = i;
347
- }
348
-
349
- if (pos > 0) {
350
- parents[i] = tailIndices[pos - 1]!;
351
- }
352
- }
353
-
354
- const keepSet = new Set<T>();
355
- let curr = tailIndices.length > 0 ? tailIndices[tailIndices.length - 1]! : -1;
356
- while (curr !== -1) {
357
- const bIdx = indices[curr]!;
358
- keepSet.add(b[start + bIdx]!);
359
- curr = parents[curr]!;
360
- }
361
-
362
- for (let i = 0; i < start; i++) {
363
- keep(a[i]!);
364
- }
365
-
366
- for (let i = 0; i < aMidLen; i++) {
367
- const val = a[start + i]!;
368
- if (!keepSet.has(val)) {
369
- dela(val);
370
- }
371
- }
372
-
373
- for (let i = 0; i < bMidLen; i++) {
374
- const val = b[start + i]!;
375
- if (keepSet.has(val)) {
376
- keep(val);
377
- } else {
378
- a2b(val);
379
- }
380
- }
381
-
382
- for (let i = 0; i < end; i++) {
383
- keep(a[aLen - end + i]!);
384
- }
385
- }
386
203
  interface MapState<T> {
387
- _itemDestroyer: Array<() => void>;
388
- _domNodesOfItem: Node[][];
389
- _arrayValue: T[];
204
+ _killer: Array<() => void>;
205
+ _nodes: Node[][];
206
+ _arr: T[];
390
207
  }
391
208
  export const map = <T>(array: () => T[], f: (item: T) => Node[]): Node[] => {
392
209
  return fragment(() => {
393
210
  const headNode = fragmentHeadNode;
394
- cleanup(() => {
395
- mapState()._itemDestroyer.forEach((f) => f());
396
- });
397
- const createItem = (item: T) => {
398
- return root((destroyItem) => {
399
- const domNodes = f(item);
400
- return [destroyItem, domNodes] as [() => void, Node[]];
401
- });
402
- };
211
+ cleanup(() => mapState()._killer.forEach((f) => f()));
212
+ const createItem = (item: T) => root((destroyItem) => [destroyItem, f(item)] as [() => void, Node[]]);
403
213
  const mapState = S<MapState<T>>(
404
214
  (
405
215
  preState = {
406
- _arrayValue: [] as T[],
407
- _domNodesOfItem: [],
408
- _itemDestroyer: [],
216
+ _arr: [] as T[],
217
+ _nodes: [],
218
+ _killer: [],
409
219
  },
410
220
  ) => {
411
221
  const currentArrayValue = array();
412
- const preArrayValue = preState._arrayValue;
222
+ const preArrayValue = preState._arr;
413
223
  const newState: MapState<T> = {
414
- _arrayValue: currentArrayValue,
415
- _domNodesOfItem: [],
416
- _itemDestroyer: [],
224
+ _arr: currentArrayValue,
225
+ _nodes: [],
226
+ _killer: [],
417
227
  };
418
- arrTrans(
228
+ arr(
419
229
  preArrayValue,
420
230
  currentArrayValue,
231
+ (oldItem) => preState._killer[preArrayValue.indexOf(oldItem)]!(),
421
232
  (newItem) => {
422
233
  const [destroyer, domNodes] = createItem(newItem);
423
- const preNode = newState._domNodesOfItem.at(-1)?.at(-1) ?? headNode;
424
- (preNode as Element | Comment).after(...domNodes);
425
- newState._domNodesOfItem.push(domNodes);
426
- newState._itemDestroyer.push(destroyer);
234
+ const preNode = (newState._nodes.at(-1)?.at(-1) ?? headNode) as Element | Comment;
235
+ preNode.after(...domNodes);
236
+ newState._nodes.push(domNodes);
237
+ newState._killer.push(destroyer);
427
238
  },
428
239
  (oldItem) => {
429
240
  const i = preArrayValue.indexOf(oldItem);
430
- preState._itemDestroyer[i]!();
241
+ newState._nodes.push(preState._nodes[i]!);
242
+ newState._killer.push(preState._killer[i]!);
431
243
  },
432
244
  (oldItem) => {
433
245
  const i = preArrayValue.indexOf(oldItem);
434
- newState._domNodesOfItem.push(preState._domNodesOfItem[i]!);
435
- newState._itemDestroyer.push(preState._itemDestroyer[i]!);
246
+ const preNode = (newState._nodes.at(-1)?.at(-1) ?? headNode) as Element | Comment;
247
+ const domNodes = preState._nodes[i]!;
248
+ preNode.after(...domNodes);
249
+ newState._nodes.push(domNodes);
250
+ newState._killer.push(preState._killer[i]!);
436
251
  },
437
252
  );
438
253
  return newState;
439
254
  },
440
255
  );
441
- return sample(() => mapState()._domNodesOfItem.flat());
256
+ return sample(() => mapState()._nodes.flat());
442
257
  });
443
258
  };
444
259
 
445
- const isPropertyOf = (domEl: HTMLElement | SVGElement, traitName: string) => {
446
- let descriptor;
447
- do {
448
- descriptor = Object.getOwnPropertyDescriptor(domEl, traitName);
449
- domEl = Object.getPrototypeOf(domEl);
450
- } while (domEl && !descriptor);
451
- if (!descriptor) {
452
- return false;
453
- }
454
- if (false === descriptor.writable) {
455
- return false;
456
- }
457
- if (descriptor.get && !descriptor.set) {
458
- return false;
459
- }
460
- return true;
260
+ const isPropertyOf = (el: HTMLElement | SVGElement, key: string) => {
261
+ if (!(key in el)) return false;
262
+ let d;
263
+ while (el && !(d = Object.getOwnPropertyDescriptor(el, key))) el = Object.getPrototypeOf(el);
264
+ return d && d.writable !== false && !!(!d.get || d.set);
461
265
  };
462
266
 
463
267
  const attribute = (node: Element, key: string | (() => string), val: string | (() => any)) => {
464
- if (typeof key !== 'function' && !isReactive(val)) {
465
- if (!node) {
466
- return;
467
- }
468
- if (!(node instanceof Element)) {
469
- return;
470
- }
471
- const isProp = isPropertyOf(node as any, key);
472
- if (isProp) {
473
- const preValue = (node as any)[key];
268
+ if (!(node instanceof Element)) {
269
+ return;
270
+ }
271
+ if (!isFun(key) && !isReactive(val)) {
272
+ if (isPropertyOf(node as any, key)) {
474
273
  (node as any)[key] = val;
475
- cleanup(() => {
476
- (node as any)[key] = preValue;
477
- });
478
274
  } else {
479
275
  node.setAttribute(key, String(val));
480
- cleanup(() => {
481
- node.removeAttribute(key);
482
- });
483
276
  }
484
277
  } else {
485
- S(() => {
486
- attribute(node, typeof key === 'function' ? key() : key, isReactive(val) ? val() : val);
487
- });
278
+ S(() => attribute(node, callif(key), isReactive(val) ? val() : val));
488
279
  }
489
280
  };
490
281
 
@@ -500,31 +291,29 @@ const spreadAttrs = (node: Element, value: () => any) => {
500
291
  const tree2dom = (ns: string | null, node: InterTreeNode, values: any[]): Node[] => {
501
292
  if (node._type === InterNodeType.Text) {
502
293
  return node._data.flatMap((part) => {
503
- if (typeof part === 'string') {
504
- const textNode = new Text(part);
505
- cleanup(() => textNode.remove());
506
- return textNode;
507
- } else if (typeof part === 'number') {
508
- const value = values[part];
509
- if (typeof value === 'function') {
510
- if (isReactive(value)) {
511
- return fragment(() => value());
512
- } else {
513
- return fragment(value);
514
- }
515
- } else if (typeof value !== 'object') {
516
- const textNode = new Text(String(value));
517
- cleanup(() => textNode.remove());
518
- return textNode;
519
- }
520
- return value;
294
+ if (isStr(part)) {
295
+ return deferRemove(new Text(part));
296
+ }
297
+ const value = values[part];
298
+ if (isFun(value)) {
299
+ return fragment(value);
300
+ } else if (typeof value !== 'object') {
301
+ return deferRemove(new Text(String(value)));
521
302
  }
303
+ return value;
522
304
  });
523
305
  } else {
524
306
  const { _attrs: attrs, _content: content, _tag: tag, _sourceNode: sourceNode } = node;
525
307
  const tagValue = unparseText(tag, values);
526
308
  const currentNs = node._ns ?? ns;
527
- return element(currentNs, sourceNode, tagValue, attrs, values, () => {
309
+ return element(currentNs, sourceNode, tagValue, (el) => {
310
+ for (const { _name: name, _value: value } of attrs) {
311
+ if (name[0] === '...' && isNum(name[1])) {
312
+ spreadAttrs(el, unparseText(name.slice(1), values));
313
+ } else {
314
+ attribute(el, unparseText(name, values), unparseText(value, values));
315
+ }
316
+ }
528
317
  return content.flatMap((t) => tree2dom(currentNs, t, values));
529
318
  });
530
319
  }
@@ -550,35 +339,19 @@ const cloneTreeNode = (treeNode: InterTreeNode, parentNode: Element | null): Int
550
339
  };
551
340
 
552
341
  const tag =
553
- (
554
- ns: string | null,
555
- docType: DOMParserSupportedType,
556
- wrapSource: (source: string) => string,
557
- unwrapNode: (node: Document) => NodeList,
558
- ) =>
342
+ (ns: string | null, markup2nodeList: (markup: string) => NodeList) =>
559
343
  (segments: TemplateStringsArray, ...values: any[]): Node[] => {
560
344
  const tree = parse(
561
- docType,
562
- wrapSource,
563
- unwrapNode,
345
+ markup2nodeList,
564
346
  segments,
565
347
  values.map((it) => values.indexOf(it)),
566
348
  );
567
349
  const clonedTree = tree.map((tree) => cloneTreeNode(tree, null));
568
- return fragment(() => {
569
- return clonedTree.flatMap((item) => tree2dom(ns, item, values));
570
- });
350
+ return fragment(() => clonedTree.flatMap((item) => tree2dom(ns, item, values)));
571
351
  };
572
352
  const svgns = 'http://www.w3.org/2000/svg';
573
353
  export const svg = tag(
574
354
  svgns,
575
- 'text/xml',
576
- (svg) => `<svg xmlns="${svgns}">${svg}</svg>`,
577
- (doc) => doc.firstChild!.childNodes,
578
- );
579
- export const html = tag(
580
- null,
581
- 'text/html',
582
- (html) => html,
583
- (doc) => doc.body.childNodes,
355
+ (svgStr) => domParser.parseFromString(`<svg xmlns="${svgns}">${svgStr}</svg>`, 'text/xml').firstChild!.childNodes,
584
356
  );
357
+ export const html = tag(null, (htmlStr) => domParser.parseFromString(htmlStr, 'text/html').body.childNodes);
package/src/s.ts CHANGED
@@ -159,7 +159,7 @@ export const transaction = <T>(f: () => T): T => {
159
159
  return result;
160
160
  };
161
161
 
162
- export { o as observable };
162
+ export { o as observable, S as computed };
163
163
 
164
164
  export const sample = <T>(f: () => T): T => {
165
165
  const fallback = currentUpdate;