subay 0.0.7 → 0.0.10

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,11 +1,8 @@
1
- /** @format */
2
-
3
1
  import { cleanup, isReactive, root, S, sample } from './s.js';
4
2
 
5
3
  const enum InterNodeType {
6
4
  Text = 1,
7
5
  Element = 2,
8
- Attribute = 3,
9
6
  }
10
7
 
11
8
  type InterTreeNode = InterTextNode | InterElementNode;
@@ -25,9 +22,8 @@ type InterElementNode = {
25
22
  };
26
23
 
27
24
  type InterAttributeNode = {
28
- _type: InterNodeType.Attribute;
29
25
  _name: Array<string | number>;
30
- _value: Array<string | number>;
26
+ _value: Array<string | number | true>;
31
27
  };
32
28
 
33
29
  const cache = new Map<TemplateStringsArray, InterTreeNode[]>();
@@ -66,7 +62,7 @@ const parseText = (data: string) => {
66
62
  return result;
67
63
  };
68
64
 
69
- const unparseText = (data: Array<string | number>, values: any[]) => {
65
+ const unparseText = (data: Array<string | number | boolean>, values: any[]) => {
70
66
  if (data.length === 1) {
71
67
  if (typeof data[0] === 'number') {
72
68
  return values[data[0]!];
@@ -122,7 +118,7 @@ const parse = (
122
118
  ) {
123
119
  do
124
120
  remove_and_dec_index: {
125
- if (item.nodeType === Node.ELEMENT_NODE) {
121
+ if (item.nodeType === 1 /* Node.ELEMENT_NODE*/) {
126
122
  const el = item as Element;
127
123
  const tag = parseText(el.tagName.toLowerCase());
128
124
  const isVariableElem = tag.length > 1 || typeof tag[0] === 'number';
@@ -147,9 +143,8 @@ const parse = (
147
143
  }
148
144
  return [
149
145
  {
150
- _type: InterNodeType.Attribute,
151
146
  _name: name,
152
- _value: value,
147
+ _value: value.length === 0 ? [true] : value,
153
148
  } satisfies InterAttributeNode,
154
149
  ];
155
150
  }),
@@ -161,7 +156,7 @@ const parse = (
161
156
  if (!isVariableElem) {
162
157
  break remove_and_dec_index;
163
158
  }
164
- } else if (item.nodeType === Node.TEXT_NODE) {
159
+ } else if (item.nodeType === 3 /*Node.TEXT_NODE*/) {
165
160
  const data = (item as Text).data;
166
161
  const result = parseText(data);
167
162
  if (result.length > 0) {
@@ -191,29 +186,49 @@ const parse = (
191
186
  const element = (
192
187
  ns: string | null,
193
188
  sourceNode: Element | null,
194
- tagName: string | (() => string),
195
- child: (node: Element) => Node[],
189
+ tagName: string | (() => string) | ((...props: any[]) => Node[]),
190
+ attrs: InterAttributeNode[],
191
+ values: any[],
192
+ child: () => Node[],
196
193
  ): Node[] => {
197
194
  if (typeof tagName === 'function') {
198
- return fragment(() => {
199
- const realTagName = tagName();
200
- if (typeof realTagName === 'string') {
201
- return element(ns, null, realTagName, child);
202
- }
203
- return [];
204
- });
195
+ if (isReactive(tagName)) {
196
+ return fragment(() => {
197
+ const realTagName = tagName();
198
+ return element(ns, null, realTagName, attrs, values, child);
199
+ });
200
+ } else {
201
+ return fragment(() => {
202
+ return tagName(
203
+ ...attrs.flatMap(({ _name: name, _value: value }) => {
204
+ if (name[0] === '...' && typeof name[1] === 'number') {
205
+ return unparseText(name.slice(1), values);
206
+ } else {
207
+ return unparseText(value, values);
208
+ }
209
+ }),
210
+ child,
211
+ ) as Node[];
212
+ });
213
+ }
205
214
  }
206
215
  if (typeof tagName !== 'string') {
207
216
  return [];
208
217
  }
209
218
  const el = sourceNode || (ns ? document.createElementNS(ns, tagName) : document.createElement(tagName));
210
219
  S(() => {
211
- const subnodes = child(el);
220
+ for (const { _name: name, _value: value } of attrs) {
221
+ if (name[0] === '...' && typeof name[1] === 'number') {
222
+ spreadAttrs(el, unparseText(name.slice(1), values));
223
+ } else {
224
+ attribute(el, unparseText(name, values), unparseText(value, values));
225
+ }
226
+ }
227
+ const subnodes = child();
212
228
  if (subnodes.length === 0) {
213
229
  return;
214
230
  }
215
231
  if (!el.firstChild) {
216
- // @domact
217
232
  el.append(...subnodes);
218
233
  } else {
219
234
  for (let left: Element | null = el.firstElementChild, rightIndex = 0; rightIndex < subnodes.length; rightIndex++) {
@@ -221,7 +236,6 @@ const element = (
221
236
  if (left === right) {
222
237
  left = left.nextElementSibling;
223
238
  } else {
224
- // @domact
225
239
  el.insertBefore(right, left);
226
240
  }
227
241
  }
@@ -230,20 +244,21 @@ const element = (
230
244
  cleanup(() => el.remove());
231
245
  return [el];
232
246
  };
233
-
234
- const fragment = (child: (head: Node) => Node[]): Node[] => {
235
- const firstNode = document.createComment('start-frag');
247
+ let fragmentHeadNode: null | Comment = null;
248
+ const fragment = (child: () => Node[]): Node[] => {
249
+ const firstNode = new Comment('');
236
250
  cleanup(() => firstNode.remove());
237
251
  const tempArray: Node[] = [firstNode];
238
252
  S(() => {
239
- const c = child(firstNode);
253
+ fragmentHeadNode = firstNode;
254
+ const c = child();
240
255
  const carr = Array.isArray(c) ? c : [c];
241
256
 
242
257
  const nodified = carr.map((c) => {
243
258
  if (c instanceof Node) {
244
259
  return c;
245
260
  }
246
- const node = document.createTextNode(c);
261
+ const node = new Text(c);
247
262
  cleanup(() => node.remove());
248
263
  return node;
249
264
  });
@@ -373,7 +388,8 @@ interface MapState<T> {
373
388
  _arrayValue: T[];
374
389
  }
375
390
  export const map = <T>(array: () => T[], f: (item: T) => Node[]): Node[] => {
376
- return fragment((headNode) => {
391
+ return fragment(() => {
392
+ const headNode = fragmentHeadNode;
377
393
  cleanup(() => {
378
394
  mapState()._itemDestroyer.forEach((f) => f());
379
395
  });
@@ -442,6 +458,7 @@ const isPropertyOf = (domEl: HTMLElement | SVGElement, traitName: string) => {
442
458
  }
443
459
  return true;
444
460
  };
461
+
445
462
  const attribute = (node: Element, key: string | (() => string), val: string | (() => any)) => {
446
463
  if (typeof key !== 'function' && !isReactive(val)) {
447
464
  if (!node) {
@@ -458,7 +475,7 @@ const attribute = (node: Element, key: string | (() => string), val: string | ((
458
475
  (node as any)[key] = preValue;
459
476
  });
460
477
  } else {
461
- node.setAttribute(key, val);
478
+ node.setAttribute(key, String(val));
462
479
  cleanup(() => {
463
480
  node.removeAttribute(key);
464
481
  });
@@ -483,33 +500,30 @@ const tree2dom = (ns: string | null, node: InterTreeNode, values: any[]): Node[]
483
500
  if (node._type === InterNodeType.Text) {
484
501
  return node._data.flatMap((part) => {
485
502
  if (typeof part === 'string') {
486
- const textNode = document.createTextNode(part);
503
+ const textNode = new Text(part);
487
504
  cleanup(() => textNode.remove());
488
505
  return textNode;
489
506
  } else if (typeof part === 'number') {
490
507
  const value = values[part];
491
508
  if (typeof value === 'function') {
492
- return fragment(value);
493
- } else {
494
- const textNode = document.createTextNode(String(value));
509
+ if (isReactive(value)) {
510
+ return fragment(() => value());
511
+ } else {
512
+ return fragment(value);
513
+ }
514
+ } else if (typeof value !== 'object') {
515
+ const textNode = new Text(String(value));
495
516
  cleanup(() => textNode.remove());
496
517
  return textNode;
497
518
  }
519
+ return value;
498
520
  }
499
- return [];
500
521
  });
501
522
  } else {
502
523
  const { _attrs: attrs, _content: content, _tag: tag, _sourceNode: sourceNode } = node;
503
524
  const tagValue = unparseText(tag, values);
504
525
  const currentNs = node._ns ?? ns;
505
- return element(currentNs, sourceNode, tagValue, (node) => {
506
- for (const { _name: name, _value: value } of attrs) {
507
- if (name[0] === '...' && typeof name[1] === 'number') {
508
- spreadAttrs(node, unparseText(name.slice(1), values));
509
- } else {
510
- attribute(node, unparseText(name, values), unparseText(value, values));
511
- }
512
- }
526
+ return element(currentNs, sourceNode, tagValue, attrs, values, () => {
513
527
  return content.flatMap((t) => tree2dom(currentNs, t, values));
514
528
  });
515
529
  }
package/src/index.ts CHANGED
@@ -1,4 +1,2 @@
1
- /** @format */
2
-
3
1
  export * from './s.js';
4
2
  export * from './h.js';
package/src/name.ts ADDED
@@ -0,0 +1,60 @@
1
+ const url2src = new Map<string, string>();
2
+ const getLeftName = function getLeftName() {
3
+ const stack = new Error().stack;
4
+ if (!stack) {
5
+ return null;
6
+ }
7
+ const urlRegex = /((?:\w+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\),\$_\{\}\^~\[\]`#|]+):(\d+):(\d+)/;
8
+ const lines = stack.split('\n');
9
+
10
+ const currentLineIndex = lines.findIndex((line) => {
11
+ return line.includes(import.meta.url) && line.includes(getLeftName.name);
12
+ });
13
+
14
+ if (currentLineIndex > -1) {
15
+ const offset = 3;
16
+ const line = lines[currentLineIndex + offset];
17
+ if (!line) {
18
+ return null;
19
+ }
20
+ const location = line.match(urlRegex);
21
+ if (!location) {
22
+ return null;
23
+ }
24
+ const url = location[1];
25
+ const lineNo = location[2];
26
+ const colNo = location[3];
27
+ if (!url || !lineNo || !colNo) {
28
+ return null;
29
+ }
30
+ let src: string;
31
+ if (url2src.has(url)) {
32
+ src = url2src.get(url)!;
33
+ } else {
34
+ const xhr = new XMLHttpRequest();
35
+ xhr.open('GET', url, false);
36
+ xhr.send();
37
+ src = xhr.responseText;
38
+ url2src.set(url, src);
39
+ }
40
+
41
+ const reg = new RegExp(`(?:.*\r?\n){${Number(lineNo) - 1}}.{${Number(colNo) - 1}}`);
42
+ const segment = src.match(reg);
43
+ if (!segment) {
44
+ return null;
45
+ }
46
+ return segment[0].match(/(\w+)\s*=\s*$/)?.[1];
47
+ }
48
+ return null;
49
+ };
50
+
51
+ export const namedFunc = <F extends (...args: any[]) => any>(f: F, fallback?: string): F => {
52
+ const leftName = getLeftName() ?? fallback;
53
+ if (!leftName) {
54
+ return f;
55
+ }
56
+ const g = new Function(`return function ${leftName} (...args) {return ${leftName}._(...args)}`)();
57
+ g._ = f;
58
+ Object.setPrototypeOf(g, f);
59
+ return g;
60
+ };
package/src/s.ts CHANGED
@@ -1,167 +1,169 @@
1
- interface IUpdater {
2
- (): void;
3
- _whoIOwn: Array<IUpdater>;
4
- _whoINeed: Array<ITrigger>;
5
- _dirty: boolean;
6
- _onCleanupCallback: Array<() => void>;
7
- }
1
+ declare const __SUBAY_DEV__: boolean | undefined;
8
2
 
9
- interface ITrigger {
10
- _whoNeedMe: Array<IUpdater>;
11
- _data: IO<any>;
12
- _pendingValue: any;
13
- }
3
+ const namedFunc = typeof __SUBAY_DEV__ === 'boolean' && __SUBAY_DEV__ ? (await import('./name.js')).namedFunc : <T>(f: T) => f;
14
4
 
15
5
  interface IS<T> {
16
- (): T;
6
+ (force?: typeof Yes): T;
7
+ _whoIOwn: Array<IS<any>>;
8
+ _whoINeed: Set<IO<any> | IS<any>>;
9
+ _whoNeedMe: Array<IS<any>>;
10
+ _dirty: boolean;
11
+ _onCleanupCallback: Array<() => void>;
12
+ $o: number;
13
+ _lastAccess: number;
17
14
  }
18
15
 
16
+ type IPartialS<T> = {
17
+ (force?: typeof Yes): T;
18
+ _whoIOwn?: Array<IS<any>>;
19
+ _whoINeed?: Set<IO<any> | IS<any>>;
20
+ _whoNeedMe?: Array<IS<any>>;
21
+ _dirty?: boolean;
22
+ _onCleanupCallback?: Array<() => void>;
23
+ $o?: number;
24
+ _lastAccess?: number;
25
+ };
26
+
19
27
  interface IO<T> {
20
28
  (): T;
21
29
  (nextValue: T): T;
30
+ _whoNeedMe: Array<IS<any>>;
31
+ _pendingValue: any;
32
+ $o: number;
22
33
  }
23
34
 
24
- let currentUpdate: IUpdater | undefined = undefined;
35
+ let currentUpdate: IS<any> | undefined = undefined;
25
36
  const NoValue: unique symbol = Symbol();
26
- let triggerQueue: Array<ITrigger> | undefined = undefined;
27
- const fun2s = new WeakMap<(...args: any[]) => any, IUpdater>();
28
- class Reactive {}
29
- class Observable extends Reactive {}
30
- class Computation extends Reactive {}
31
- const cProto = new Computation();
32
- const oProto = new Observable();
37
+ const Yes: unique symbol = Symbol();
38
+ let triggerQueue: Array<IO<any>> | undefined = undefined;
39
+ const fun2s = new WeakMap<(...args: any[]) => any, IS<any>>();
40
+ let clock = 0;
33
41
 
34
42
  export const root = <T>(fn: (destroy: () => void) => T): T => {
35
43
  const backup = currentUpdate;
36
- const rootUpdate: IUpdater = (() => {}) as any;
37
- resetUpdate(rootUpdate);
44
+ const rootUpdate: IS<any> = (() => {}) as any;
45
+ resetS(rootUpdate);
38
46
  rootUpdate._dirty = false;
39
47
  currentUpdate = rootUpdate;
40
48
  const result = fn(() => {
41
- recycleUpdate(rootUpdate);
49
+ recycleS(rootUpdate);
42
50
  currentUpdate = undefined;
43
51
  });
44
52
  currentUpdate = backup;
45
53
  return result;
46
54
  };
47
55
  export const S = <T>(fn: (pv: T | undefined) => T, value?: T): IS<T> => {
48
- const data = () => {
49
- if (update._dirty) {
50
- update();
51
- } else if (currentUpdate) {
52
- update._whoINeed.forEach((t) => t._data());
53
- }
54
- return value as T;
55
- };
56
- Object.setPrototypeOf(data, cProto);
57
- const update: IUpdater = (() => {
58
- const backup = currentUpdate;
59
- recycleUpdate(update);
60
- update._dirty = false;
61
- currentUpdate = update;
62
- value = fn(value);
63
- currentUpdate = backup;
64
- }) as any;
56
+ const data: IS<T> = namedFunc(
57
+ resetS((force?: typeof Yes) => {
58
+ if (force === Yes || data._dirty) {
59
+ data._lastAccess = clock;
60
+ const backup = currentUpdate;
61
+ recycleS(data);
62
+ data._dirty = false;
63
+ currentUpdate = data;
64
+ value = fn(value);
65
+ currentUpdate = backup;
66
+ } else if (currentUpdate && !currentUpdate._whoINeed.has(data)) {
67
+ currentUpdate._whoINeed.add(data);
68
+ data._whoNeedMe.push(currentUpdate);
69
+ }
70
+ return value as T;
71
+ }),
72
+ );
73
+ data.$o = 0xc03;
74
+ data._lastAccess = clock++;
65
75
  if (currentUpdate) {
66
- currentUpdate._whoIOwn.push(update);
76
+ currentUpdate._whoIOwn.push(data);
67
77
  }
68
- resetUpdate(update);
69
- update();
70
- fun2s.set(fn, update);
78
+ data(Yes);
79
+ fun2s.set(fn, data);
71
80
  return data;
72
81
  };
73
82
  export const o = <T>(value: T): IO<T> => {
74
- function data(): T;
75
- function data(nextValue: T): T;
76
- function data(nextValue?: T) {
83
+ function d(): T;
84
+ function d(nextValue: T): T;
85
+ function d(nextValue?: T) {
77
86
  if (arguments.length === 0) {
78
- if (currentUpdate) {
79
- if (!currentUpdate._whoINeed.includes(trigger)) {
80
- currentUpdate._whoINeed.push(trigger);
81
- }
82
- if (!trigger._whoNeedMe.includes(currentUpdate)) {
83
- trigger._whoNeedMe.push(currentUpdate);
84
- }
87
+ if (currentUpdate && !currentUpdate._whoINeed.has(data)) {
88
+ currentUpdate._whoINeed.add(data);
89
+ data._whoNeedMe.push(currentUpdate);
85
90
  }
86
91
  return value;
87
92
  }
88
-
93
+ clock++;
89
94
  if (triggerQueue) {
90
- if (trigger._pendingValue === NoValue) {
91
- triggerQueue.push(trigger);
95
+ if (data._pendingValue === NoValue) {
96
+ triggerQueue.push(data);
92
97
  }
93
- trigger._pendingValue = nextValue;
98
+ (data as IO<T>)._pendingValue = nextValue as T;
94
99
  return nextValue;
95
100
  }
96
-
97
101
  value = nextValue!;
98
-
99
102
  const backup = currentUpdate;
100
103
  currentUpdate = undefined;
101
-
102
- const whoNeedMeLasttime = trigger._whoNeedMe.filter((it) => {
103
- return it._whoINeed.includes(trigger);
104
- });
104
+ const collection = new Set<IS<any>>();
105
+ const collect = (it: IS<any>, by: IS<any> | IO<any>): void => {
106
+ if (!it._whoINeed.has(by) || collection.has(it)) {
107
+ return;
108
+ }
109
+ collection.add(it);
110
+ it._whoNeedMe.forEach((i) => collect(i, it));
111
+ };
112
+ data._whoNeedMe.forEach((it) => collect(it, data));
113
+ const whoNeedMeLasttime = Array.from(collection);
105
114
  whoNeedMeLasttime.forEach((u) => {
106
115
  u._dirty = true;
107
116
  });
108
- trigger._whoNeedMe = [];
109
- whoNeedMeLasttime.forEach((u) => {
117
+ data._whoNeedMe = [];
118
+ whoNeedMeLasttime.sort(byLastAccess).forEach((u) => {
110
119
  if (u._dirty) {
111
120
  u();
112
121
  }
113
122
  });
114
-
115
123
  currentUpdate = backup;
116
-
117
124
  return value;
118
125
  }
119
-
120
- Object.setPrototypeOf(data, oProto);
121
-
122
- const trigger: ITrigger = {
123
- _whoNeedMe: [],
124
- _data: data,
125
- _pendingValue: NoValue,
126
- };
127
-
126
+ const data = namedFunc(d as IO<T>);
127
+ data._whoNeedMe = [] as Array<IS<any>>;
128
+ data._pendingValue = NoValue;
129
+ data.$o = 0x0b5;
130
+ clock++;
128
131
  return data;
129
132
  };
130
- const recycleUpdate = (update: IUpdater) => {
131
- update._whoIOwn.forEach(recycleUpdate);
133
+ const byLastAccess = (a: IS<any>, b: IS<any>) => a._lastAccess - b._lastAccess;
134
+ const recycleS = (update: IS<any>) => {
135
+ update._whoIOwn.forEach(recycleS);
132
136
  update._dirty = false;
133
137
  update._onCleanupCallback.forEach((f) => f());
134
- resetUpdate(update);
138
+ resetS(update);
135
139
  };
136
140
 
137
- const resetUpdate = (update: IUpdater) => {
138
- update._whoINeed = [];
141
+ const resetS = (update: IPartialS<any>) => {
142
+ update._whoINeed = new Set();
143
+ update._whoNeedMe = [];
139
144
  update._whoIOwn = [];
140
145
  update._onCleanupCallback = [];
146
+ return update as IS<any>;
141
147
  };
142
148
 
143
149
  export const cleanup = <T extends () => void>(f: T): T => {
144
- if (currentUpdate) {
145
- currentUpdate._onCleanupCallback.push(f);
146
- }
150
+ currentUpdate?._onCleanupCallback.push(f);
147
151
  return f;
148
152
  };
149
153
 
150
154
  export const transaction = <T>(f: () => T): T => {
151
155
  const fallback = triggerQueue;
152
- triggerQueue = [] as ITrigger[];
156
+ triggerQueue = [] as IO<any>[];
153
157
  const result = f();
154
158
  const workedQueue = triggerQueue;
155
159
  triggerQueue = fallback;
156
-
157
160
  workedQueue.forEach((t) => {
158
161
  if (t._pendingValue !== NoValue) {
159
162
  const pv = t._pendingValue;
160
163
  t._pendingValue = NoValue;
161
- t._data(pv);
164
+ t(pv);
162
165
  }
163
166
  });
164
-
165
167
  return result;
166
168
  };
167
169
 
@@ -184,10 +186,9 @@ export const subscribe = (f: () => void) => {
184
186
  export const unsubscribe = (f: () => void) => {
185
187
  const update = fun2s.get(f);
186
188
  if (update) {
187
- recycleUpdate(update);
189
+ recycleS(update);
188
190
  }
189
191
  };
190
-
191
- export const isObservable = (o: any): o is IO<any> => o instanceof Observable;
192
- export const isComputed = (o: any): o is IS<any> => o instanceof Computation;
193
- export const isReactive = (o: any): o is IO<any> | IS<any> => o instanceof Reactive;
192
+ export const isObservable = (o: any): o is IO<any> => o?.$o === 0x0b5;
193
+ export const isComputed = (o: any): o is IS<any> => o?.$o === 0xc03;
194
+ export const isReactive = (o: any): o is IO<any> | IS<any> => o?.$o;