@matthewp/zebra 0.0.1 → 0.0.3

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/dist/html.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ export declare class SafeHTML {
2
+ #private;
3
+ constructor(value: string);
4
+ toString(): string;
5
+ }
6
+ export declare function unsafeHTML(s: string): SafeHTML;
7
+ export declare function html(strings: TemplateStringsArray, ...values: unknown[]): SafeHTML;
8
+ //# sourceMappingURL=html.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../src/html.ts"],"names":[],"mappings":"AAYA,qBAAa,QAAQ;;gBAEP,KAAK,EAAE,MAAM;IAGzB,QAAQ,IAAI,MAAM;CAGnB;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,QAAQ,CAE9C;AAYD,wBAAgB,IAAI,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,QAAQ,CAOlF"}
package/dist/html.js ADDED
@@ -0,0 +1,43 @@
1
+ const ESC = {
2
+ '&': '&',
3
+ '<': '&lt;',
4
+ '>': '&gt;',
5
+ '"': '&quot;',
6
+ "'": '&#39;',
7
+ };
8
+ function escapeHtml(s) {
9
+ return s.replace(/[&<>"']/g, (ch) => ESC[ch]);
10
+ }
11
+ export class SafeHTML {
12
+ #html;
13
+ constructor(value) {
14
+ this.#html = value;
15
+ }
16
+ toString() {
17
+ return this.#html;
18
+ }
19
+ }
20
+ export function unsafeHTML(s) {
21
+ return new SafeHTML(s);
22
+ }
23
+ function resolveValue(val) {
24
+ if (val == null || val === false)
25
+ return '';
26
+ if (val instanceof SafeHTML)
27
+ return val.toString();
28
+ if (typeof val === 'object' && 'template' in val && typeof val.template === 'function') {
29
+ return val.template().toString();
30
+ }
31
+ if (Array.isArray(val))
32
+ return val.map(resolveValue).join('');
33
+ return escapeHtml(String(val));
34
+ }
35
+ export function html(strings, ...values) {
36
+ let result = strings[0];
37
+ for (let i = 0; i < values.length; i++) {
38
+ result += resolveValue(values[i]);
39
+ result += strings[i + 1];
40
+ }
41
+ return new SafeHTML(result);
42
+ }
43
+ //# sourceMappingURL=html.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.js","sourceRoot":"","sources":["../src/html.ts"],"names":[],"mappings":"AAAA,MAAM,GAAG,GAA2B;IAClC,GAAG,EAAE,OAAO;IACZ,GAAG,EAAE,MAAM;IACX,GAAG,EAAE,MAAM;IACX,GAAG,EAAE,QAAQ;IACb,GAAG,EAAE,OAAO;CACb,CAAC;AAEF,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,OAAO,QAAQ;IACnB,KAAK,CAAS;IACd,YAAY,KAAa;QACvB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IACD,QAAQ;QACN,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;CACF;AAED,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,OAAO,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC;AACzB,CAAC;AAED,SAAS,YAAY,CAAC,GAAY;IAChC,IAAI,GAAG,IAAI,IAAI,IAAI,GAAG,KAAK,KAAK;QAAE,OAAO,EAAE,CAAC;IAC5C,IAAI,GAAG,YAAY,QAAQ;QAAE,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;IACnD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,UAAU,IAAI,GAAG,IAAI,OAAQ,GAAW,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;QAChG,OAAQ,GAAW,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC;IAC5C,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC9D,OAAO,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,OAA6B,EAAE,GAAG,MAAiB;IACtE,IAAI,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACxB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAClC,MAAM,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3B,CAAC;IACD,OAAO,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC9B,CAAC"}
package/dist/list.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ import { View } from './view.ts';
2
+ import { SafeHTML } from './html.ts';
3
+ type ViewConstructor<T> = new () => View<T>;
4
+ export declare class List<T = Record<string, unknown>> {
5
+ private ViewClass;
6
+ private keyFn;
7
+ private views;
8
+ private keys;
9
+ private container;
10
+ constructor(ViewClass: ViewConstructor<T>, keyFn: (item: T) => unknown);
11
+ template(items?: T[]): SafeHTML;
12
+ mount(container: HTMLElement, items?: T[]): void;
13
+ private updateView;
14
+ update(items: T[]): void;
15
+ }
16
+ export {};
17
+ //# sourceMappingURL=list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../src/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,KAAK,eAAe,CAAC,CAAC,IAAI,UAAU,IAAI,CAAC,CAAC,CAAC,CAAC;AAE5C,qBAAa,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC3C,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,IAAI,CAAiB;IAC7B,OAAO,CAAC,SAAS,CAA4B;gBAEjC,SAAS,EAAE,eAAe,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO;IAKtE,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,GAAG,QAAQ;IAQ/B,KAAK,CAAC,SAAS,EAAE,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,EAAE;IAczC,OAAO,CAAC,UAAU;IAIlB,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE;CAuGlB"}
package/dist/list.js ADDED
@@ -0,0 +1,141 @@
1
+ import { SafeHTML } from "./html.js";
2
+ export class List {
3
+ ViewClass;
4
+ keyFn;
5
+ views = [];
6
+ keys = [];
7
+ container = null;
8
+ constructor(ViewClass, keyFn) {
9
+ this.ViewClass = ViewClass;
10
+ this.keyFn = keyFn;
11
+ }
12
+ template(items) {
13
+ if (!items || items.length === 0)
14
+ return new SafeHTML('');
15
+ return new SafeHTML(items.map(item => {
16
+ let view = new this.ViewClass();
17
+ return view.template(item).toString();
18
+ }).join(''));
19
+ }
20
+ mount(container, items) {
21
+ this.container = container;
22
+ let children = Array.from(container.children);
23
+ if (children.length > 0 && items) {
24
+ for (let i = 0; i < children.length && i < items.length; i++) {
25
+ let view = new this.ViewClass();
26
+ view.mount(children[i]);
27
+ view.update(items[i]);
28
+ this.views.push(view);
29
+ this.keys.push(this.keyFn(items[i]));
30
+ }
31
+ }
32
+ }
33
+ updateView(view, item) {
34
+ view.update(item);
35
+ }
36
+ update(items) {
37
+ let container = this.container;
38
+ let oldViews = this.views;
39
+ let oldKeys = this.keys;
40
+ let newKeys = items.map(this.keyFn);
41
+ let newViews = new Array(items.length).fill(null);
42
+ let oldHead = 0;
43
+ let oldTail = oldViews.length - 1;
44
+ let newHead = 0;
45
+ let newTail = items.length - 1;
46
+ let oldKeyToIndex;
47
+ while (oldHead <= oldTail && newHead <= newTail) {
48
+ if (oldViews[oldHead] === null) {
49
+ oldHead++;
50
+ }
51
+ else if (oldViews[oldTail] === null) {
52
+ oldTail--;
53
+ }
54
+ else if (oldKeys[oldHead] === newKeys[newHead]) {
55
+ // Head-Head match
56
+ newViews[newHead] = oldViews[oldHead];
57
+ this.updateView(oldViews[oldHead], items[newHead]);
58
+ oldHead++;
59
+ newHead++;
60
+ }
61
+ else if (oldKeys[oldTail] === newKeys[newTail]) {
62
+ // Tail-Tail match
63
+ newViews[newTail] = oldViews[oldTail];
64
+ this.updateView(oldViews[oldTail], items[newTail]);
65
+ oldTail--;
66
+ newTail--;
67
+ }
68
+ else if (oldKeys[oldHead] === newKeys[newTail]) {
69
+ // Head-Tail match: move old head to after old tail
70
+ newViews[newTail] = oldViews[oldHead];
71
+ this.updateView(oldViews[oldHead], items[newTail]);
72
+ oldViews[oldTail].el.after(oldViews[oldHead].el);
73
+ oldHead++;
74
+ newTail--;
75
+ }
76
+ else if (oldKeys[oldTail] === newKeys[newHead]) {
77
+ // Tail-Head match: move old tail to before old head
78
+ newViews[newHead] = oldViews[oldTail];
79
+ this.updateView(oldViews[oldTail], items[newHead]);
80
+ oldViews[oldHead].el.before(oldViews[oldTail].el);
81
+ oldTail--;
82
+ newHead++;
83
+ }
84
+ else {
85
+ // Build map lazily
86
+ if (!oldKeyToIndex) {
87
+ oldKeyToIndex = new Map();
88
+ for (let i = oldHead; i <= oldTail; i++) {
89
+ if (oldViews[i] !== null) {
90
+ oldKeyToIndex.set(oldKeys[i], i);
91
+ }
92
+ }
93
+ }
94
+ let oldIndex = oldKeyToIndex.get(newKeys[newHead]);
95
+ if (oldIndex === undefined) {
96
+ // New item
97
+ let view = new this.ViewClass();
98
+ view.createAndMount();
99
+ newViews[newHead] = view;
100
+ oldViews[oldHead].el.before(view.el);
101
+ this.updateView(view, items[newHead]);
102
+ }
103
+ else {
104
+ // Move existing
105
+ let view = oldViews[oldIndex];
106
+ this.updateView(view, items[newHead]);
107
+ newViews[newHead] = view;
108
+ oldViews[oldHead].el.before(view.el);
109
+ oldViews[oldIndex] = null;
110
+ }
111
+ newHead++;
112
+ }
113
+ }
114
+ // Remove remaining old items
115
+ while (oldHead <= oldTail) {
116
+ if (oldViews[oldHead] !== null) {
117
+ oldViews[oldHead].el.remove();
118
+ }
119
+ oldHead++;
120
+ }
121
+ // Add remaining new items
122
+ while (newHead <= newTail) {
123
+ let view = new this.ViewClass();
124
+ view.createAndMount();
125
+ newViews[newHead] = view;
126
+ let el = view.el;
127
+ let ref = newViews[newTail + 1]?.el;
128
+ if (ref) {
129
+ ref.before(el);
130
+ }
131
+ else {
132
+ container.append(el);
133
+ }
134
+ this.updateView(view, items[newHead]);
135
+ newHead++;
136
+ }
137
+ this.views = newViews;
138
+ this.keys = newKeys;
139
+ }
140
+ }
141
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.js","sourceRoot":"","sources":["../src/list.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { View } from './view.ts';
2
+ export declare function renderToString(view: View, props?: Record<string, unknown>): string;
3
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtC,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAElF"}
package/dist/server.js ADDED
@@ -0,0 +1,4 @@
1
+ export function renderToString(view, props) {
2
+ return view.template(props).toString();
3
+ }
4
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,cAAc,CAAC,IAAU,EAAE,KAA+B;IACxE,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC;AACzC,CAAC"}
package/dist/view.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ export { SafeHTML, html, unsafeHTML } from './html.ts';
2
+ import { SafeHTML } from './html.ts';
3
+ export declare class View<T = Record<string, unknown>> {
4
+ el: HTMLElement;
5
+ createElement(): HTMLElement;
6
+ template(_props?: T): SafeHTML;
7
+ createAndMount(): void;
8
+ mount(el: HTMLElement): void;
9
+ update(_data?: T): HTMLElement;
10
+ }
11
+ interface Slottable {
12
+ template(props?: any): SafeHTML;
13
+ }
14
+ export declare function slot<T extends Slottable>(target: T, ...args: Parameters<T['template']>): SafeHTML;
15
+ //# sourceMappingURL=view.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../src/view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAQ,MAAM,WAAW,CAAC;AAE3C,qBAAa,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC3C,EAAE,EAAG,WAAW,CAAC;IAEjB,aAAa,IAAI,WAAW;IAO5B,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,QAAQ;IAI9B,cAAc,IAAI,IAAI;IAItB,KAAK,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI;IAI5B,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,WAAW;CAG/B;AAED,UAAU,SAAS;IACjB,QAAQ,CAAC,KAAK,CAAC,EAAE,GAAG,GAAG,QAAQ,CAAC;CACjC;AAED,wBAAgB,IAAI,CAAC,CAAC,SAAS,SAAS,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,GAAG,QAAQ,CAEjG"}
package/dist/view.js ADDED
@@ -0,0 +1,27 @@
1
+ export { SafeHTML, html, unsafeHTML } from "./html.js";
2
+ import { html } from "./html.js";
3
+ export class View {
4
+ el;
5
+ createElement() {
6
+ let tpl = document.createElement('template');
7
+ tpl.innerHTML = this.template().toString();
8
+ this.el = document.importNode(tpl.content, true).firstElementChild;
9
+ return this.el;
10
+ }
11
+ template(_props) {
12
+ return html ``;
13
+ }
14
+ createAndMount() {
15
+ this.mount(this.createElement());
16
+ }
17
+ mount(el) {
18
+ this.el = el;
19
+ }
20
+ update(_data) {
21
+ return this.el;
22
+ }
23
+ }
24
+ export function slot(target, ...args) {
25
+ return target.template(...args);
26
+ }
27
+ //# sourceMappingURL=view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view.js","sourceRoot":"","sources":["../src/view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAY,IAAI,EAAE,MAAM,WAAW,CAAC;AAE3C,MAAM,OAAO,IAAI;IACf,EAAE,CAAe;IAEjB,aAAa;QACX,IAAI,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;QAC7C,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC;QAC3C,IAAI,CAAC,EAAE,GAAG,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,iBAAgC,CAAC;QAClF,OAAO,IAAI,CAAC,EAAE,CAAC;IACjB,CAAC;IAED,QAAQ,CAAC,MAAU;QACjB,OAAO,IAAI,CAAA,EAAE,CAAC;IAChB,CAAC;IAED,cAAc;QACZ,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,EAAe;QACnB,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;IACf,CAAC;IAED,MAAM,CAAC,KAAS;QACd,OAAO,IAAI,CAAC,EAAE,CAAC;IACjB,CAAC;CACF;AAMD,MAAM,UAAU,IAAI,CAAsB,MAAS,EAAE,GAAG,IAA+B;IACrF,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,CAAC;AAClC,CAAC"}
package/package.json CHANGED
@@ -1,10 +1,30 @@
1
1
  {
2
2
  "name": "@matthewp/zebra",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "type": "module",
5
5
  "exports": {
6
- ".": "./src/view.ts",
7
- "./list": "./src/list.ts",
8
- "./server": "./src/server.ts"
6
+ ".": {
7
+ "types": "./dist/view.d.ts",
8
+ "default": "./dist/view.js"
9
+ },
10
+ "./html": {
11
+ "types": "./dist/html.d.ts",
12
+ "default": "./dist/html.js"
13
+ },
14
+ "./list": {
15
+ "types": "./dist/list.d.ts",
16
+ "default": "./dist/list.js"
17
+ },
18
+ "./server": {
19
+ "types": "./dist/server.d.ts",
20
+ "default": "./dist/server.js"
21
+ }
22
+ },
23
+ "files": ["dist"],
24
+ "scripts": {
25
+ "build": "tsc"
26
+ },
27
+ "devDependencies": {
28
+ "typescript": "^5.8.0"
9
29
  }
10
30
  }
package/src/list.ts DELETED
@@ -1,146 +0,0 @@
1
- import { View } from './view.ts';
2
-
3
- type ViewConstructor = new () => View;
4
-
5
- export class List<T = Record<string, unknown>> {
6
- private ViewClass: ViewConstructor;
7
- private keyFn: (item: T) => unknown;
8
- private views: (View | null)[] = [];
9
- private keys: unknown[] = [];
10
- private container: HTMLElement | null = null;
11
-
12
- constructor(ViewClass: ViewConstructor, keyFn: (item: T) => unknown) {
13
- this.ViewClass = ViewClass;
14
- this.keyFn = keyFn;
15
- }
16
-
17
- template(items?: T[]): string {
18
- if (!items || items.length === 0) return '';
19
- return items.map(item => {
20
- let view = new this.ViewClass();
21
- return view.template(item as unknown as Record<string, unknown>);
22
- }).join('');
23
- }
24
-
25
- mount(container: HTMLElement, items?: T[]) {
26
- this.container = container;
27
- let children = Array.from(container.children) as HTMLElement[];
28
- if (children.length > 0 && items) {
29
- for (let i = 0; i < children.length && i < items.length; i++) {
30
- let view = new this.ViewClass();
31
- view.mount(children[i]);
32
- view.update(items[i] as unknown as Record<string, unknown>);
33
- this.views.push(view);
34
- this.keys.push(this.keyFn(items[i]));
35
- }
36
- }
37
- }
38
-
39
- private updateView(view: View, item: T) {
40
- view.update(item as unknown as Record<string, unknown>);
41
- }
42
-
43
- update(items: T[]) {
44
- let container = this.container!;
45
- let oldViews = this.views;
46
- let oldKeys = this.keys;
47
- let newKeys = items.map(this.keyFn);
48
- let newViews: (View | null)[] = new Array(items.length).fill(null);
49
-
50
- let oldHead = 0;
51
- let oldTail = oldViews.length - 1;
52
- let newHead = 0;
53
- let newTail = items.length - 1;
54
-
55
- let oldKeyToIndex: Map<unknown, number> | undefined;
56
-
57
- while (oldHead <= oldTail && newHead <= newTail) {
58
- if (oldViews[oldHead] === null) {
59
- oldHead++;
60
- } else if (oldViews[oldTail] === null) {
61
- oldTail--;
62
- } else if (oldKeys[oldHead] === newKeys[newHead]) {
63
- // Head-Head match
64
- newViews[newHead] = oldViews[oldHead];
65
- this.updateView(oldViews[oldHead]!, items[newHead]);
66
- oldHead++;
67
- newHead++;
68
- } else if (oldKeys[oldTail] === newKeys[newTail]) {
69
- // Tail-Tail match
70
- newViews[newTail] = oldViews[oldTail];
71
- this.updateView(oldViews[oldTail]!, items[newTail]);
72
- oldTail--;
73
- newTail--;
74
- } else if (oldKeys[oldHead] === newKeys[newTail]) {
75
- // Head-Tail match: move old head to after old tail
76
- newViews[newTail] = oldViews[oldHead];
77
- this.updateView(oldViews[oldHead]!, items[newTail]);
78
- oldViews[oldTail]!.el.after(oldViews[oldHead]!.el);
79
- oldHead++;
80
- newTail--;
81
- } else if (oldKeys[oldTail] === newKeys[newHead]) {
82
- // Tail-Head match: move old tail to before old head
83
- newViews[newHead] = oldViews[oldTail];
84
- this.updateView(oldViews[oldTail]!, items[newHead]);
85
- oldViews[oldHead]!.el.before(oldViews[oldTail]!.el);
86
- oldTail--;
87
- newHead++;
88
- } else {
89
- // Build map lazily
90
- if (!oldKeyToIndex) {
91
- oldKeyToIndex = new Map();
92
- for (let i = oldHead; i <= oldTail; i++) {
93
- if (oldViews[i] !== null) {
94
- oldKeyToIndex.set(oldKeys[i], i);
95
- }
96
- }
97
- }
98
-
99
- let oldIndex = oldKeyToIndex.get(newKeys[newHead]);
100
- if (oldIndex === undefined) {
101
- // New item
102
- let view = new this.ViewClass();
103
- view.createAndMount();
104
- newViews[newHead] = view;
105
- oldViews[oldHead]!.el.before(view.el);
106
- this.updateView(view, items[newHead]);
107
- } else {
108
- // Move existing
109
- let view = oldViews[oldIndex]!;
110
- this.updateView(view, items[newHead]);
111
- newViews[newHead] = view;
112
- oldViews[oldHead]!.el.before(view.el);
113
- oldViews[oldIndex] = null;
114
- }
115
- newHead++;
116
- }
117
- }
118
-
119
- // Remove remaining old items
120
- while (oldHead <= oldTail) {
121
- if (oldViews[oldHead] !== null) {
122
- oldViews[oldHead]!.el.remove();
123
- }
124
- oldHead++;
125
- }
126
-
127
- // Add remaining new items
128
- while (newHead <= newTail) {
129
- let view = new this.ViewClass();
130
- view.createAndMount();
131
- newViews[newHead] = view;
132
- let el = view.el;
133
- let ref = newViews[newTail + 1]?.el;
134
- if (ref) {
135
- ref.before(el);
136
- } else {
137
- container.append(el);
138
- }
139
- this.updateView(view, items[newHead]);
140
- newHead++;
141
- }
142
-
143
- this.views = newViews;
144
- this.keys = newKeys;
145
- }
146
- }
package/src/server.ts DELETED
@@ -1,5 +0,0 @@
1
- import type { View } from './view.ts';
2
-
3
- export function renderToString(view: View, props?: Record<string, unknown>): string {
4
- return view.template(props);
5
- }
package/src/view.ts DELETED
@@ -1,34 +0,0 @@
1
- export class View {
2
- el!: HTMLElement;
3
-
4
- createElement(): HTMLElement {
5
- let tpl = document.createElement('template');
6
- tpl.innerHTML = this.template();
7
- this.el = document.importNode(tpl.content, true).firstElementChild as HTMLElement;
8
- return this.el;
9
- }
10
-
11
- template(_props?: any): string {
12
- return '';
13
- }
14
-
15
- createAndMount(): void {
16
- this.mount(this.createElement());
17
- }
18
-
19
- mount(el: HTMLElement): void {
20
- this.el = el;
21
- }
22
-
23
- update(_data?: Record<string, unknown>): HTMLElement {
24
- return this.el;
25
- }
26
- }
27
-
28
- interface Slottable {
29
- template(props?: any): string;
30
- }
31
-
32
- export function slot<T extends Slottable>(target: T, ...args: Parameters<T['template']>): string {
33
- return target.template(...args);
34
- }