@ktjs/core 0.15.6 → 0.16.1
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/README.md +64 -2
- package/dist/index.d.ts +62 -6
- package/dist/index.iife.js +199 -1
- package/dist/index.legacy.js +199 -1
- package/dist/index.mjs +198 -2
- package/dist/jsx/index.d.ts +2 -4
- package/dist/jsx/index.mjs +1 -1
- package/dist/jsx/jsx-runtime.d.ts +2 -4
- package/dist/jsx/jsx-runtime.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Core DOM manipulation utilities for KT.js framework with built-in JSX/TSX suppor
|
|
|
12
12
|
|
|
13
13
|
`@ktjs/core` is the foundation of KT.js, providing the essential `h` function and DOM utilities for building web applications with direct DOM manipulation. It emphasizes performance, type safety, and minimal abstraction over native DOM APIs.
|
|
14
14
|
|
|
15
|
-
**Current Version:** 0.
|
|
15
|
+
**Current Version:** 0.16.0
|
|
16
16
|
|
|
17
17
|
## Features
|
|
18
18
|
|
|
@@ -28,6 +28,12 @@ Core DOM manipulation utilities for KT.js framework with built-in JSX/TSX suppor
|
|
|
28
28
|
- `redraw()` method for controlled re-rendering
|
|
29
29
|
- **k-if directive**: Conditional element creation with `k-if` attribute
|
|
30
30
|
- Array children support for seamless list rendering
|
|
31
|
+
- **KTFor Component**: Efficient list rendering with key-based optimization (v0.16.0)
|
|
32
|
+
- Key-based DOM reuse similar to Svelte's `{#each}` blocks
|
|
33
|
+
- Minimal DOM operations - only updates what changed
|
|
34
|
+
- Returns Comment anchor node for flexible positioning
|
|
35
|
+
- Type-safe with full TypeScript generics support
|
|
36
|
+
- `KTForStatic` variant for simple lists without key optimization
|
|
31
37
|
- **KTAsync Component**: Handle async components with ease
|
|
32
38
|
- Automatic handling of Promise-based components
|
|
33
39
|
- Seamless integration with JSX/TSX
|
|
@@ -200,6 +206,62 @@ const div = (<div>Old content</div>) as KTHTMLElement;
|
|
|
200
206
|
div.redraw(undefined, 'New content');
|
|
201
207
|
```
|
|
202
208
|
|
|
209
|
+
### List Rendering with KTFor (v0.16.0)
|
|
210
|
+
|
|
211
|
+
The `KTFor` component provides efficient list rendering with key-based DOM reuse:
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
import { KTFor, KTForStatic, KTForAnchor } from '@ktjs/core';
|
|
215
|
+
|
|
216
|
+
interface Todo {
|
|
217
|
+
id: number;
|
|
218
|
+
text: string;
|
|
219
|
+
done: boolean;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
let todos: Todo[] = [
|
|
223
|
+
{ id: 1, text: 'Buy milk', done: false },
|
|
224
|
+
{ id: 2, text: 'Write code', done: true },
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
// Create optimized list with key-based reuse
|
|
228
|
+
const todoList = (
|
|
229
|
+
<KTFor
|
|
230
|
+
list={todos}
|
|
231
|
+
key={(item) => item.id} // Stable key for efficient updates
|
|
232
|
+
mapper={(item, index) => (
|
|
233
|
+
<div class={`todo ${item.done ? 'done' : ''}`}>
|
|
234
|
+
<input type="checkbox" checked={item.done} />
|
|
235
|
+
<span>{item.text}</span>
|
|
236
|
+
<button on:click={() => deleteTodo(item.id)}>Delete</button>
|
|
237
|
+
</div>
|
|
238
|
+
)}
|
|
239
|
+
/>
|
|
240
|
+
) as KTForAnchor;
|
|
241
|
+
|
|
242
|
+
// Add to DOM (anchor + all items are rendered)
|
|
243
|
+
document.body.appendChild(todoList);
|
|
244
|
+
|
|
245
|
+
// Update the list - only changed items are updated
|
|
246
|
+
todos = [...todos, { id: 3, text: 'New task', done: false }];
|
|
247
|
+
todoList.redraw({ list: todos });
|
|
248
|
+
|
|
249
|
+
// For simple static lists, use KTForStatic (no key optimization)
|
|
250
|
+
const simpleList = (
|
|
251
|
+
<KTForStatic list={['Apple', 'Banana', 'Orange']} mapper={(item) => <div>{item}</div>} />
|
|
252
|
+
) as KTForAnchor;
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**How it works:**
|
|
256
|
+
|
|
257
|
+
- Returns a Comment node (`<!-- kt-for -->`) as anchor point
|
|
258
|
+
- All list items are rendered as siblings after the anchor
|
|
259
|
+
- Uses key-based diff algorithm to reuse DOM nodes
|
|
260
|
+
- Only adds/removes/moves nodes that changed
|
|
261
|
+
- Similar to Svelte's `{#each}` blocks
|
|
262
|
+
|
|
263
|
+
````
|
|
264
|
+
|
|
203
265
|
### Array Children Support (v0.14.1+)
|
|
204
266
|
|
|
205
267
|
Children can now be arrays for easier list rendering:
|
|
@@ -231,7 +293,7 @@ const TodoList = ({ todos }: { todos: string[] }) => (
|
|
|
231
293
|
</ul>
|
|
232
294
|
</div>
|
|
233
295
|
);
|
|
234
|
-
|
|
296
|
+
````
|
|
235
297
|
|
|
236
298
|
### Async Components
|
|
237
299
|
|
package/dist/index.d.ts
CHANGED
|
@@ -116,9 +116,7 @@ interface KTBaseAttribute {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
type KTPrefixedEventHandlers = {
|
|
119
|
-
[EventName in keyof HTMLElementEventMap as `on:${EventName}`]?:
|
|
120
|
-
| ((ev: HTMLElementEventMap[EventName]) => void)
|
|
121
|
-
| (Function & {});
|
|
119
|
+
[EventName in keyof HTMLElementEventMap as `on:${EventName}`]?: (ev: HTMLElementEventMap[EventName]) => void;
|
|
122
120
|
};
|
|
123
121
|
|
|
124
122
|
type KTSpecialEventHandlers = {
|
|
@@ -170,7 +168,7 @@ type HTML<T extends (HTMLTag | SVGTag) & otherstring> = T extends SVGTag ? SVGEl
|
|
|
170
168
|
* ## About
|
|
171
169
|
* @package @ktjs/core
|
|
172
170
|
* @author Kasukabe Tsumugi <futami16237@gmail.com>
|
|
173
|
-
* @version 0.
|
|
171
|
+
* @version 0.16.1 (Last Update: 2026.01.28 08:51:19.714)
|
|
174
172
|
* @license MIT
|
|
175
173
|
* @link https://github.com/baendlorel/kt.js
|
|
176
174
|
* @link https://baendlorel.github.io/ Welcome to my site!
|
|
@@ -244,5 +242,63 @@ declare function KTAsync<T extends KTComponent>(props: {
|
|
|
244
242
|
children?: KTRawContent;
|
|
245
243
|
} & ExtractComponentProps<T>): KTHTMLElement;
|
|
246
244
|
|
|
247
|
-
|
|
248
|
-
|
|
245
|
+
interface KTForProps<T> {
|
|
246
|
+
list: T[];
|
|
247
|
+
/**
|
|
248
|
+
* Key function to uniquely identify each item
|
|
249
|
+
* Using stable keys enables efficient DOM reuse and updates
|
|
250
|
+
*/
|
|
251
|
+
key: (item: T, index: number, array: T[]) => string | number;
|
|
252
|
+
/**
|
|
253
|
+
* Mapper function to render each item
|
|
254
|
+
*/
|
|
255
|
+
mapper: (item: T, index: number, array: T[]) => HTMLElement;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Extended Comment node with redraw capability for KTFor
|
|
259
|
+
*/
|
|
260
|
+
interface KTForAnchor extends Comment {
|
|
261
|
+
redraw: (newProps?: {
|
|
262
|
+
list?: any[];
|
|
263
|
+
mapper?: (item: any, index: number, array: any[]) => HTMLElement;
|
|
264
|
+
}) => void;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* KTFor component - Efficient list rendering with key-based DOM reuse
|
|
268
|
+
* Similar to Svelte's {#each} blocks, provides optimized updates
|
|
269
|
+
*
|
|
270
|
+
* Returns a Comment node as anchor point. All list items are rendered after this anchor.
|
|
271
|
+
*
|
|
272
|
+
* @example
|
|
273
|
+
* ```tsx
|
|
274
|
+
* const listAnchor = <KTFor
|
|
275
|
+
* list={items}
|
|
276
|
+
* key={(item) => item.id}
|
|
277
|
+
* mapper={(item) => <div>{item.name}</div>}
|
|
278
|
+
* /> as KTForAnchor;
|
|
279
|
+
*
|
|
280
|
+
* document.body.appendChild(listAnchor); // Anchor + all items are rendered
|
|
281
|
+
*
|
|
282
|
+
* // Update the list
|
|
283
|
+
* listAnchor.redraw({ list: newItems });
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
declare function KTFor<T>(props: KTForProps<T>): KTForAnchor;
|
|
287
|
+
/**
|
|
288
|
+
* Simple list rendering without key optimization
|
|
289
|
+
* Rebuilds the entire list on each update - use for small static lists
|
|
290
|
+
*
|
|
291
|
+
* Returns a Comment anchor node. All items are rendered after this anchor.
|
|
292
|
+
*
|
|
293
|
+
* @example
|
|
294
|
+
* ```tsx
|
|
295
|
+
* const listAnchor = <KTForConst
|
|
296
|
+
* list={items}
|
|
297
|
+
* mapper={(item) => <div>{item}</div>}
|
|
298
|
+
* /> as KTForAnchor;
|
|
299
|
+
* ```
|
|
300
|
+
*/
|
|
301
|
+
declare function KTForConst<T>(props: Omit<KTForProps<T>, 'key'>): KTForAnchor;
|
|
302
|
+
|
|
303
|
+
export { Fragment, KTAsync, KTFor, KTForConst, h as createElement, createRedrawable, createRedrawableNoref, h, jsx, jsxDEV, jsxs, ref };
|
|
304
|
+
export type { EventHandler, HTMLTag, KTAttribute, KTForAnchor, KTForProps, KTHTMLElement, KTRawAttr, KTRawContent, KTRawContents, KTRef };
|
package/dist/index.iife.js
CHANGED
|
@@ -206,7 +206,7 @@ var __ktjs_core__ = (function (exports) {
|
|
|
206
206
|
* ## About
|
|
207
207
|
* @package @ktjs/core
|
|
208
208
|
* @author Kasukabe Tsumugi <futami16237@gmail.com>
|
|
209
|
-
* @version 0.
|
|
209
|
+
* @version 0.16.1 (Last Update: 2026.01.28 08:51:19.714)
|
|
210
210
|
* @license MIT
|
|
211
211
|
* @link https://github.com/baendlorel/kt.js
|
|
212
212
|
* @link https://baendlorel.github.io/ Welcome to my site!
|
|
@@ -378,8 +378,206 @@ var __ktjs_core__ = (function (exports) {
|
|
|
378
378
|
return comp;
|
|
379
379
|
}
|
|
380
380
|
|
|
381
|
+
/**
|
|
382
|
+
* KTFor component - Efficient list rendering with key-based DOM reuse
|
|
383
|
+
* Similar to Svelte's {#each} blocks, provides optimized updates
|
|
384
|
+
*
|
|
385
|
+
* Returns a Comment node as anchor point. All list items are rendered after this anchor.
|
|
386
|
+
*
|
|
387
|
+
* @example
|
|
388
|
+
* ```tsx
|
|
389
|
+
* const listAnchor = <KTFor
|
|
390
|
+
* list={items}
|
|
391
|
+
* key={(item) => item.id}
|
|
392
|
+
* mapper={(item) => <div>{item.name}</div>}
|
|
393
|
+
* /> as KTForAnchor;
|
|
394
|
+
*
|
|
395
|
+
* document.body.appendChild(listAnchor); // Anchor + all items are rendered
|
|
396
|
+
*
|
|
397
|
+
* // Update the list
|
|
398
|
+
* listAnchor.redraw({ list: newItems });
|
|
399
|
+
* ```
|
|
400
|
+
*/
|
|
401
|
+
function KTFor(props) {
|
|
402
|
+
const { list: initList, key: getKey, mapper } = props;
|
|
403
|
+
// Cache: key -> { node: HTMLElement, item: T }
|
|
404
|
+
const nodeCache = new Map();
|
|
405
|
+
// Current key order
|
|
406
|
+
let currentKeys = [];
|
|
407
|
+
// Create anchor comment node - marks the position of the list
|
|
408
|
+
const anchor = document.createComment('kt-for');
|
|
409
|
+
/**
|
|
410
|
+
* Get all nodes currently in the DOM that belong to this list
|
|
411
|
+
* They are all siblings after the anchor node
|
|
412
|
+
*/
|
|
413
|
+
const getListNodes = () => {
|
|
414
|
+
const nodes = [];
|
|
415
|
+
if (!anchor.parentNode)
|
|
416
|
+
return nodes;
|
|
417
|
+
let current = anchor.nextSibling;
|
|
418
|
+
new Set(currentKeys);
|
|
419
|
+
// Collect nodes that belong to this list
|
|
420
|
+
while (current) {
|
|
421
|
+
const isListNode = Array.from(nodeCache.values()).some((cached) => cached.node === current);
|
|
422
|
+
if (!isListNode)
|
|
423
|
+
break;
|
|
424
|
+
nodes.push(current);
|
|
425
|
+
current = current.nextSibling;
|
|
426
|
+
}
|
|
427
|
+
return nodes;
|
|
428
|
+
};
|
|
429
|
+
/**
|
|
430
|
+
* Initialize list - append all initial items after anchor
|
|
431
|
+
*/
|
|
432
|
+
const initialize = () => {
|
|
433
|
+
for (let i = 0; i < initList.length; i++) {
|
|
434
|
+
const item = initList[i];
|
|
435
|
+
const key = getKey(item, i, initList);
|
|
436
|
+
const node = mapper(item, i, initList);
|
|
437
|
+
nodeCache.set(key, { node, item });
|
|
438
|
+
currentKeys.push(key);
|
|
439
|
+
// Append to parent if anchor is already in DOM
|
|
440
|
+
if (anchor.parentNode) {
|
|
441
|
+
const lastNode = getListNodes()[getListNodes().length - 1];
|
|
442
|
+
if (lastNode) {
|
|
443
|
+
lastNode.after(node);
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
anchor.after(node);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
/**
|
|
452
|
+
* Smart update method - only modifies DOM nodes that changed
|
|
453
|
+
* Uses key-based diff algorithm to minimize DOM operations
|
|
454
|
+
*/
|
|
455
|
+
const smartUpdate = (newList, newMapper) => {
|
|
456
|
+
const mapperFn = newMapper ?? mapper;
|
|
457
|
+
const newKeys = newList.map((item, index) => getKey(item, index, newList));
|
|
458
|
+
if (!anchor.parentNode) {
|
|
459
|
+
// If anchor is not in DOM yet, just update cache
|
|
460
|
+
nodeCache.clear();
|
|
461
|
+
currentKeys = [];
|
|
462
|
+
for (let i = 0; i < newList.length; i++) {
|
|
463
|
+
const item = newList[i];
|
|
464
|
+
const key = newKeys[i];
|
|
465
|
+
const node = mapperFn(item, i, newList);
|
|
466
|
+
nodeCache.set(key, { node, item });
|
|
467
|
+
currentKeys.push(key);
|
|
468
|
+
}
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
// Find keys to remove (exist in current but not in new)
|
|
472
|
+
const keysToRemove = currentKeys.filter((k) => !newKeys.includes(k));
|
|
473
|
+
for (let i = 0; i < keysToRemove.length; i++) {
|
|
474
|
+
const key = keysToRemove[i];
|
|
475
|
+
const cached = nodeCache.get(key);
|
|
476
|
+
if (cached && cached.node.parentNode) {
|
|
477
|
+
cached.node.parentNode.removeChild(cached.node);
|
|
478
|
+
}
|
|
479
|
+
nodeCache.delete(key);
|
|
480
|
+
}
|
|
481
|
+
// Update/add nodes in correct order
|
|
482
|
+
let previousNode = anchor;
|
|
483
|
+
for (let i = 0; i < newList.length; i++) {
|
|
484
|
+
const item = newList[i];
|
|
485
|
+
const key = newKeys[i];
|
|
486
|
+
let cached = nodeCache.get(key);
|
|
487
|
+
// New item: create and cache
|
|
488
|
+
if (!cached) {
|
|
489
|
+
const node = mapperFn(item, i, newList);
|
|
490
|
+
cached = { node, item };
|
|
491
|
+
nodeCache.set(key, cached);
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
// Update cached item reference
|
|
495
|
+
cached.item = item;
|
|
496
|
+
}
|
|
497
|
+
// Ensure node is at correct position (right after previousNode)
|
|
498
|
+
if (previousNode.nextSibling !== cached.node) {
|
|
499
|
+
if (previousNode.nextSibling) {
|
|
500
|
+
anchor.parentNode.insertBefore(cached.node, previousNode.nextSibling);
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
anchor.parentNode.appendChild(cached.node);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
previousNode = cached.node;
|
|
507
|
+
}
|
|
508
|
+
currentKeys = newKeys;
|
|
509
|
+
};
|
|
510
|
+
// Mount redraw method on anchor
|
|
511
|
+
anchor.redraw = (newProps) => {
|
|
512
|
+
if (newProps?.list) {
|
|
513
|
+
smartUpdate(newProps.list, newProps.mapper);
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
// Initialize the list
|
|
517
|
+
initialize();
|
|
518
|
+
return anchor;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Simple list rendering without key optimization
|
|
522
|
+
* Rebuilds the entire list on each update - use for small static lists
|
|
523
|
+
*
|
|
524
|
+
* Returns a Comment anchor node. All items are rendered after this anchor.
|
|
525
|
+
*
|
|
526
|
+
* @example
|
|
527
|
+
* ```tsx
|
|
528
|
+
* const listAnchor = <KTForConst
|
|
529
|
+
* list={items}
|
|
530
|
+
* mapper={(item) => <div>{item}</div>}
|
|
531
|
+
* /> as KTForAnchor;
|
|
532
|
+
* ```
|
|
533
|
+
*/
|
|
534
|
+
function KTForConst(props) {
|
|
535
|
+
const { list: initList, mapper } = props;
|
|
536
|
+
// Create anchor comment node
|
|
537
|
+
const anchor = document.createComment('kt-for-const');
|
|
538
|
+
let nodes = [];
|
|
539
|
+
// Simple rebuild on redraw
|
|
540
|
+
const rebuild = (newList) => {
|
|
541
|
+
// Remove all old nodes
|
|
542
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
543
|
+
const node = nodes[i];
|
|
544
|
+
if (node.parentNode) {
|
|
545
|
+
node.parentNode.removeChild(node);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
nodes = [];
|
|
549
|
+
// Create new nodes
|
|
550
|
+
let previousNode = anchor;
|
|
551
|
+
for (let i = 0; i < newList.length; i++) {
|
|
552
|
+
const item = newList[i];
|
|
553
|
+
const node = mapper(item, i, newList);
|
|
554
|
+
nodes.push(node);
|
|
555
|
+
if (anchor.parentNode) {
|
|
556
|
+
if (previousNode.nextSibling) {
|
|
557
|
+
anchor.parentNode.insertBefore(node, previousNode.nextSibling);
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
anchor.parentNode.appendChild(node);
|
|
561
|
+
}
|
|
562
|
+
previousNode = node;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
// Initial render
|
|
567
|
+
rebuild(initList);
|
|
568
|
+
// Mount redraw
|
|
569
|
+
anchor.redraw = (newProps) => {
|
|
570
|
+
if (newProps?.list) {
|
|
571
|
+
rebuild(newProps.list);
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
return anchor;
|
|
575
|
+
}
|
|
576
|
+
|
|
381
577
|
exports.Fragment = Fragment;
|
|
382
578
|
exports.KTAsync = KTAsync;
|
|
579
|
+
exports.KTFor = KTFor;
|
|
580
|
+
exports.KTForConst = KTForConst;
|
|
383
581
|
exports.createElement = h;
|
|
384
582
|
exports.createRedrawable = createRedrawable;
|
|
385
583
|
exports.createRedrawableNoref = createRedrawableNoref;
|
package/dist/index.legacy.js
CHANGED
|
@@ -231,7 +231,7 @@ var __ktjs_core__ = (function (exports) {
|
|
|
231
231
|
* ## About
|
|
232
232
|
* @package @ktjs/core
|
|
233
233
|
* @author Kasukabe Tsumugi <futami16237@gmail.com>
|
|
234
|
-
* @version 0.
|
|
234
|
+
* @version 0.16.1 (Last Update: 2026.01.28 08:51:19.714)
|
|
235
235
|
* @license MIT
|
|
236
236
|
* @link https://github.com/baendlorel/kt.js
|
|
237
237
|
* @link https://baendlorel.github.io/ Welcome to my site!
|
|
@@ -443,8 +443,206 @@ var __ktjs_core__ = (function (exports) {
|
|
|
443
443
|
return comp;
|
|
444
444
|
}
|
|
445
445
|
|
|
446
|
+
/**
|
|
447
|
+
* KTFor component - Efficient list rendering with key-based DOM reuse
|
|
448
|
+
* Similar to Svelte's {#each} blocks, provides optimized updates
|
|
449
|
+
*
|
|
450
|
+
* Returns a Comment node as anchor point. All list items are rendered after this anchor.
|
|
451
|
+
*
|
|
452
|
+
* @example
|
|
453
|
+
* ```tsx
|
|
454
|
+
* const listAnchor = <KTFor
|
|
455
|
+
* list={items}
|
|
456
|
+
* key={(item) => item.id}
|
|
457
|
+
* mapper={(item) => <div>{item.name}</div>}
|
|
458
|
+
* /> as KTForAnchor;
|
|
459
|
+
*
|
|
460
|
+
* document.body.appendChild(listAnchor); // Anchor + all items are rendered
|
|
461
|
+
*
|
|
462
|
+
* // Update the list
|
|
463
|
+
* listAnchor.redraw({ list: newItems });
|
|
464
|
+
* ```
|
|
465
|
+
*/
|
|
466
|
+
function KTFor(props) {
|
|
467
|
+
var initList = props.list, getKey = props.key, mapper = props.mapper;
|
|
468
|
+
// Cache: key -> { node: HTMLElement, item: T }
|
|
469
|
+
var nodeCache = new Map();
|
|
470
|
+
// Current key order
|
|
471
|
+
var currentKeys = [];
|
|
472
|
+
// Create anchor comment node - marks the position of the list
|
|
473
|
+
var anchor = document.createComment('kt-for');
|
|
474
|
+
/**
|
|
475
|
+
* Get all nodes currently in the DOM that belong to this list
|
|
476
|
+
* They are all siblings after the anchor node
|
|
477
|
+
*/
|
|
478
|
+
var getListNodes = function () {
|
|
479
|
+
var nodes = [];
|
|
480
|
+
if (!anchor.parentNode)
|
|
481
|
+
return nodes;
|
|
482
|
+
var current = anchor.nextSibling;
|
|
483
|
+
new Set(currentKeys);
|
|
484
|
+
// Collect nodes that belong to this list
|
|
485
|
+
while (current) {
|
|
486
|
+
var isListNode = Array.from(nodeCache.values()).some(function (cached) { return cached.node === current; });
|
|
487
|
+
if (!isListNode)
|
|
488
|
+
break;
|
|
489
|
+
nodes.push(current);
|
|
490
|
+
current = current.nextSibling;
|
|
491
|
+
}
|
|
492
|
+
return nodes;
|
|
493
|
+
};
|
|
494
|
+
/**
|
|
495
|
+
* Initialize list - append all initial items after anchor
|
|
496
|
+
*/
|
|
497
|
+
var initialize = function () {
|
|
498
|
+
for (var i = 0; i < initList.length; i++) {
|
|
499
|
+
var item = initList[i];
|
|
500
|
+
var key = getKey(item, i, initList);
|
|
501
|
+
var node = mapper(item, i, initList);
|
|
502
|
+
nodeCache.set(key, { node: node, item: item });
|
|
503
|
+
currentKeys.push(key);
|
|
504
|
+
// Append to parent if anchor is already in DOM
|
|
505
|
+
if (anchor.parentNode) {
|
|
506
|
+
var lastNode = getListNodes()[getListNodes().length - 1];
|
|
507
|
+
if (lastNode) {
|
|
508
|
+
lastNode.after(node);
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
anchor.after(node);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
/**
|
|
517
|
+
* Smart update method - only modifies DOM nodes that changed
|
|
518
|
+
* Uses key-based diff algorithm to minimize DOM operations
|
|
519
|
+
*/
|
|
520
|
+
var smartUpdate = function (newList, newMapper) {
|
|
521
|
+
var mapperFn = newMapper !== null && newMapper !== void 0 ? newMapper : mapper;
|
|
522
|
+
var newKeys = newList.map(function (item, index) { return getKey(item, index, newList); });
|
|
523
|
+
if (!anchor.parentNode) {
|
|
524
|
+
// If anchor is not in DOM yet, just update cache
|
|
525
|
+
nodeCache.clear();
|
|
526
|
+
currentKeys = [];
|
|
527
|
+
for (var i = 0; i < newList.length; i++) {
|
|
528
|
+
var item = newList[i];
|
|
529
|
+
var key = newKeys[i];
|
|
530
|
+
var node = mapperFn(item, i, newList);
|
|
531
|
+
nodeCache.set(key, { node: node, item: item });
|
|
532
|
+
currentKeys.push(key);
|
|
533
|
+
}
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
// Find keys to remove (exist in current but not in new)
|
|
537
|
+
var keysToRemove = currentKeys.filter(function (k) { return !newKeys.includes(k); });
|
|
538
|
+
for (var i = 0; i < keysToRemove.length; i++) {
|
|
539
|
+
var key = keysToRemove[i];
|
|
540
|
+
var cached = nodeCache.get(key);
|
|
541
|
+
if (cached && cached.node.parentNode) {
|
|
542
|
+
cached.node.parentNode.removeChild(cached.node);
|
|
543
|
+
}
|
|
544
|
+
nodeCache.delete(key);
|
|
545
|
+
}
|
|
546
|
+
// Update/add nodes in correct order
|
|
547
|
+
var previousNode = anchor;
|
|
548
|
+
for (var i = 0; i < newList.length; i++) {
|
|
549
|
+
var item = newList[i];
|
|
550
|
+
var key = newKeys[i];
|
|
551
|
+
var cached = nodeCache.get(key);
|
|
552
|
+
// New item: create and cache
|
|
553
|
+
if (!cached) {
|
|
554
|
+
var node = mapperFn(item, i, newList);
|
|
555
|
+
cached = { node: node, item: item };
|
|
556
|
+
nodeCache.set(key, cached);
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
// Update cached item reference
|
|
560
|
+
cached.item = item;
|
|
561
|
+
}
|
|
562
|
+
// Ensure node is at correct position (right after previousNode)
|
|
563
|
+
if (previousNode.nextSibling !== cached.node) {
|
|
564
|
+
if (previousNode.nextSibling) {
|
|
565
|
+
anchor.parentNode.insertBefore(cached.node, previousNode.nextSibling);
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
anchor.parentNode.appendChild(cached.node);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
previousNode = cached.node;
|
|
572
|
+
}
|
|
573
|
+
currentKeys = newKeys;
|
|
574
|
+
};
|
|
575
|
+
// Mount redraw method on anchor
|
|
576
|
+
anchor.redraw = function (newProps) {
|
|
577
|
+
if (newProps === null || newProps === void 0 ? void 0 : newProps.list) {
|
|
578
|
+
smartUpdate(newProps.list, newProps.mapper);
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
// Initialize the list
|
|
582
|
+
initialize();
|
|
583
|
+
return anchor;
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Simple list rendering without key optimization
|
|
587
|
+
* Rebuilds the entire list on each update - use for small static lists
|
|
588
|
+
*
|
|
589
|
+
* Returns a Comment anchor node. All items are rendered after this anchor.
|
|
590
|
+
*
|
|
591
|
+
* @example
|
|
592
|
+
* ```tsx
|
|
593
|
+
* const listAnchor = <KTForConst
|
|
594
|
+
* list={items}
|
|
595
|
+
* mapper={(item) => <div>{item}</div>}
|
|
596
|
+
* /> as KTForAnchor;
|
|
597
|
+
* ```
|
|
598
|
+
*/
|
|
599
|
+
function KTForConst(props) {
|
|
600
|
+
var initList = props.list, mapper = props.mapper;
|
|
601
|
+
// Create anchor comment node
|
|
602
|
+
var anchor = document.createComment('kt-for-const');
|
|
603
|
+
var nodes = [];
|
|
604
|
+
// Simple rebuild on redraw
|
|
605
|
+
var rebuild = function (newList) {
|
|
606
|
+
// Remove all old nodes
|
|
607
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
608
|
+
var node = nodes[i];
|
|
609
|
+
if (node.parentNode) {
|
|
610
|
+
node.parentNode.removeChild(node);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
nodes = [];
|
|
614
|
+
// Create new nodes
|
|
615
|
+
var previousNode = anchor;
|
|
616
|
+
for (var i = 0; i < newList.length; i++) {
|
|
617
|
+
var item = newList[i];
|
|
618
|
+
var node = mapper(item, i, newList);
|
|
619
|
+
nodes.push(node);
|
|
620
|
+
if (anchor.parentNode) {
|
|
621
|
+
if (previousNode.nextSibling) {
|
|
622
|
+
anchor.parentNode.insertBefore(node, previousNode.nextSibling);
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
anchor.parentNode.appendChild(node);
|
|
626
|
+
}
|
|
627
|
+
previousNode = node;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
// Initial render
|
|
632
|
+
rebuild(initList);
|
|
633
|
+
// Mount redraw
|
|
634
|
+
anchor.redraw = function (newProps) {
|
|
635
|
+
if (newProps === null || newProps === void 0 ? void 0 : newProps.list) {
|
|
636
|
+
rebuild(newProps.list);
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
return anchor;
|
|
640
|
+
}
|
|
641
|
+
|
|
446
642
|
exports.Fragment = Fragment;
|
|
447
643
|
exports.KTAsync = KTAsync;
|
|
644
|
+
exports.KTFor = KTFor;
|
|
645
|
+
exports.KTForConst = KTForConst;
|
|
448
646
|
exports.createElement = h;
|
|
449
647
|
exports.createRedrawable = createRedrawable;
|
|
450
648
|
exports.createRedrawableNoref = createRedrawableNoref;
|
package/dist/index.mjs
CHANGED
|
@@ -203,7 +203,7 @@ let creator = defaultCreator;
|
|
|
203
203
|
* ## About
|
|
204
204
|
* @package @ktjs/core
|
|
205
205
|
* @author Kasukabe Tsumugi <futami16237@gmail.com>
|
|
206
|
-
* @version 0.
|
|
206
|
+
* @version 0.16.1 (Last Update: 2026.01.28 08:51:19.714)
|
|
207
207
|
* @license MIT
|
|
208
208
|
* @link https://github.com/baendlorel/kt.js
|
|
209
209
|
* @link https://baendlorel.github.io/ Welcome to my site!
|
|
@@ -375,4 +375,200 @@ function KTAsync(props) {
|
|
|
375
375
|
return comp;
|
|
376
376
|
}
|
|
377
377
|
|
|
378
|
-
|
|
378
|
+
/**
|
|
379
|
+
* KTFor component - Efficient list rendering with key-based DOM reuse
|
|
380
|
+
* Similar to Svelte's {#each} blocks, provides optimized updates
|
|
381
|
+
*
|
|
382
|
+
* Returns a Comment node as anchor point. All list items are rendered after this anchor.
|
|
383
|
+
*
|
|
384
|
+
* @example
|
|
385
|
+
* ```tsx
|
|
386
|
+
* const listAnchor = <KTFor
|
|
387
|
+
* list={items}
|
|
388
|
+
* key={(item) => item.id}
|
|
389
|
+
* mapper={(item) => <div>{item.name}</div>}
|
|
390
|
+
* /> as KTForAnchor;
|
|
391
|
+
*
|
|
392
|
+
* document.body.appendChild(listAnchor); // Anchor + all items are rendered
|
|
393
|
+
*
|
|
394
|
+
* // Update the list
|
|
395
|
+
* listAnchor.redraw({ list: newItems });
|
|
396
|
+
* ```
|
|
397
|
+
*/
|
|
398
|
+
function KTFor(props) {
|
|
399
|
+
const { list: initList, key: getKey, mapper } = props;
|
|
400
|
+
// Cache: key -> { node: HTMLElement, item: T }
|
|
401
|
+
const nodeCache = new Map();
|
|
402
|
+
// Current key order
|
|
403
|
+
let currentKeys = [];
|
|
404
|
+
// Create anchor comment node - marks the position of the list
|
|
405
|
+
const anchor = document.createComment('kt-for');
|
|
406
|
+
/**
|
|
407
|
+
* Get all nodes currently in the DOM that belong to this list
|
|
408
|
+
* They are all siblings after the anchor node
|
|
409
|
+
*/
|
|
410
|
+
const getListNodes = () => {
|
|
411
|
+
const nodes = [];
|
|
412
|
+
if (!anchor.parentNode)
|
|
413
|
+
return nodes;
|
|
414
|
+
let current = anchor.nextSibling;
|
|
415
|
+
new Set(currentKeys);
|
|
416
|
+
// Collect nodes that belong to this list
|
|
417
|
+
while (current) {
|
|
418
|
+
const isListNode = Array.from(nodeCache.values()).some((cached) => cached.node === current);
|
|
419
|
+
if (!isListNode)
|
|
420
|
+
break;
|
|
421
|
+
nodes.push(current);
|
|
422
|
+
current = current.nextSibling;
|
|
423
|
+
}
|
|
424
|
+
return nodes;
|
|
425
|
+
};
|
|
426
|
+
/**
|
|
427
|
+
* Initialize list - append all initial items after anchor
|
|
428
|
+
*/
|
|
429
|
+
const initialize = () => {
|
|
430
|
+
for (let i = 0; i < initList.length; i++) {
|
|
431
|
+
const item = initList[i];
|
|
432
|
+
const key = getKey(item, i, initList);
|
|
433
|
+
const node = mapper(item, i, initList);
|
|
434
|
+
nodeCache.set(key, { node, item });
|
|
435
|
+
currentKeys.push(key);
|
|
436
|
+
// Append to parent if anchor is already in DOM
|
|
437
|
+
if (anchor.parentNode) {
|
|
438
|
+
const lastNode = getListNodes()[getListNodes().length - 1];
|
|
439
|
+
if (lastNode) {
|
|
440
|
+
lastNode.after(node);
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
anchor.after(node);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
/**
|
|
449
|
+
* Smart update method - only modifies DOM nodes that changed
|
|
450
|
+
* Uses key-based diff algorithm to minimize DOM operations
|
|
451
|
+
*/
|
|
452
|
+
const smartUpdate = (newList, newMapper) => {
|
|
453
|
+
const mapperFn = newMapper ?? mapper;
|
|
454
|
+
const newKeys = newList.map((item, index) => getKey(item, index, newList));
|
|
455
|
+
if (!anchor.parentNode) {
|
|
456
|
+
// If anchor is not in DOM yet, just update cache
|
|
457
|
+
nodeCache.clear();
|
|
458
|
+
currentKeys = [];
|
|
459
|
+
for (let i = 0; i < newList.length; i++) {
|
|
460
|
+
const item = newList[i];
|
|
461
|
+
const key = newKeys[i];
|
|
462
|
+
const node = mapperFn(item, i, newList);
|
|
463
|
+
nodeCache.set(key, { node, item });
|
|
464
|
+
currentKeys.push(key);
|
|
465
|
+
}
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
// Find keys to remove (exist in current but not in new)
|
|
469
|
+
const keysToRemove = currentKeys.filter((k) => !newKeys.includes(k));
|
|
470
|
+
for (let i = 0; i < keysToRemove.length; i++) {
|
|
471
|
+
const key = keysToRemove[i];
|
|
472
|
+
const cached = nodeCache.get(key);
|
|
473
|
+
if (cached && cached.node.parentNode) {
|
|
474
|
+
cached.node.parentNode.removeChild(cached.node);
|
|
475
|
+
}
|
|
476
|
+
nodeCache.delete(key);
|
|
477
|
+
}
|
|
478
|
+
// Update/add nodes in correct order
|
|
479
|
+
let previousNode = anchor;
|
|
480
|
+
for (let i = 0; i < newList.length; i++) {
|
|
481
|
+
const item = newList[i];
|
|
482
|
+
const key = newKeys[i];
|
|
483
|
+
let cached = nodeCache.get(key);
|
|
484
|
+
// New item: create and cache
|
|
485
|
+
if (!cached) {
|
|
486
|
+
const node = mapperFn(item, i, newList);
|
|
487
|
+
cached = { node, item };
|
|
488
|
+
nodeCache.set(key, cached);
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
// Update cached item reference
|
|
492
|
+
cached.item = item;
|
|
493
|
+
}
|
|
494
|
+
// Ensure node is at correct position (right after previousNode)
|
|
495
|
+
if (previousNode.nextSibling !== cached.node) {
|
|
496
|
+
if (previousNode.nextSibling) {
|
|
497
|
+
anchor.parentNode.insertBefore(cached.node, previousNode.nextSibling);
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
anchor.parentNode.appendChild(cached.node);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
previousNode = cached.node;
|
|
504
|
+
}
|
|
505
|
+
currentKeys = newKeys;
|
|
506
|
+
};
|
|
507
|
+
// Mount redraw method on anchor
|
|
508
|
+
anchor.redraw = (newProps) => {
|
|
509
|
+
if (newProps?.list) {
|
|
510
|
+
smartUpdate(newProps.list, newProps.mapper);
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
// Initialize the list
|
|
514
|
+
initialize();
|
|
515
|
+
return anchor;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Simple list rendering without key optimization
|
|
519
|
+
* Rebuilds the entire list on each update - use for small static lists
|
|
520
|
+
*
|
|
521
|
+
* Returns a Comment anchor node. All items are rendered after this anchor.
|
|
522
|
+
*
|
|
523
|
+
* @example
|
|
524
|
+
* ```tsx
|
|
525
|
+
* const listAnchor = <KTForConst
|
|
526
|
+
* list={items}
|
|
527
|
+
* mapper={(item) => <div>{item}</div>}
|
|
528
|
+
* /> as KTForAnchor;
|
|
529
|
+
* ```
|
|
530
|
+
*/
|
|
531
|
+
function KTForConst(props) {
|
|
532
|
+
const { list: initList, mapper } = props;
|
|
533
|
+
// Create anchor comment node
|
|
534
|
+
const anchor = document.createComment('kt-for-const');
|
|
535
|
+
let nodes = [];
|
|
536
|
+
// Simple rebuild on redraw
|
|
537
|
+
const rebuild = (newList) => {
|
|
538
|
+
// Remove all old nodes
|
|
539
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
540
|
+
const node = nodes[i];
|
|
541
|
+
if (node.parentNode) {
|
|
542
|
+
node.parentNode.removeChild(node);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
nodes = [];
|
|
546
|
+
// Create new nodes
|
|
547
|
+
let previousNode = anchor;
|
|
548
|
+
for (let i = 0; i < newList.length; i++) {
|
|
549
|
+
const item = newList[i];
|
|
550
|
+
const node = mapper(item, i, newList);
|
|
551
|
+
nodes.push(node);
|
|
552
|
+
if (anchor.parentNode) {
|
|
553
|
+
if (previousNode.nextSibling) {
|
|
554
|
+
anchor.parentNode.insertBefore(node, previousNode.nextSibling);
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
anchor.parentNode.appendChild(node);
|
|
558
|
+
}
|
|
559
|
+
previousNode = node;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
// Initial render
|
|
564
|
+
rebuild(initList);
|
|
565
|
+
// Mount redraw
|
|
566
|
+
anchor.redraw = (newProps) => {
|
|
567
|
+
if (newProps?.list) {
|
|
568
|
+
rebuild(newProps.list);
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
return anchor;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
export { Fragment, KTAsync, KTFor, KTForConst, h as createElement, createRedrawable, createRedrawableNoref, h, jsx, jsxDEV, jsxs, ref };
|
package/dist/jsx/index.d.ts
CHANGED
|
@@ -110,9 +110,7 @@ interface KTBaseAttribute {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
type KTPrefixedEventHandlers = {
|
|
113
|
-
[EventName in keyof HTMLElementEventMap as `on:${EventName}`]?:
|
|
114
|
-
| ((ev: HTMLElementEventMap[EventName]) => void)
|
|
115
|
-
| (Function & {});
|
|
113
|
+
[EventName in keyof HTMLElementEventMap as `on:${EventName}`]?: (ev: HTMLElementEventMap[EventName]) => void;
|
|
116
114
|
};
|
|
117
115
|
|
|
118
116
|
type KTSpecialEventHandlers = {
|
|
@@ -156,7 +154,7 @@ type HTML<T extends (HTMLTag | SVGTag) & otherstring> = T extends SVGTag ? SVGEl
|
|
|
156
154
|
* ## About
|
|
157
155
|
* @package @ktjs/core
|
|
158
156
|
* @author Kasukabe Tsumugi <futami16237@gmail.com>
|
|
159
|
-
* @version 0.
|
|
157
|
+
* @version 0.16.1 (Last Update: 2026.01.28 08:51:19.714)
|
|
160
158
|
* @license MIT
|
|
161
159
|
* @link https://github.com/baendlorel/kt.js
|
|
162
160
|
* @link https://baendlorel.github.io/ Welcome to my site!
|
package/dist/jsx/index.mjs
CHANGED
|
@@ -203,7 +203,7 @@ let creator = defaultCreator;
|
|
|
203
203
|
* ## About
|
|
204
204
|
* @package @ktjs/core
|
|
205
205
|
* @author Kasukabe Tsumugi <futami16237@gmail.com>
|
|
206
|
-
* @version 0.
|
|
206
|
+
* @version 0.16.1 (Last Update: 2026.01.28 08:51:19.714)
|
|
207
207
|
* @license MIT
|
|
208
208
|
* @link https://github.com/baendlorel/kt.js
|
|
209
209
|
* @link https://baendlorel.github.io/ Welcome to my site!
|
|
@@ -104,9 +104,7 @@ interface KTBaseAttribute {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
type KTPrefixedEventHandlers = {
|
|
107
|
-
[EventName in keyof HTMLElementEventMap as `on:${EventName}`]?:
|
|
108
|
-
| ((ev: HTMLElementEventMap[EventName]) => void)
|
|
109
|
-
| (Function & {});
|
|
107
|
+
[EventName in keyof HTMLElementEventMap as `on:${EventName}`]?: (ev: HTMLElementEventMap[EventName]) => void;
|
|
110
108
|
};
|
|
111
109
|
|
|
112
110
|
type KTSpecialEventHandlers = {
|
|
@@ -150,7 +148,7 @@ type HTML<T extends (HTMLTag | SVGTag) & otherstring> = T extends SVGTag ? SVGEl
|
|
|
150
148
|
* ## About
|
|
151
149
|
* @package @ktjs/core
|
|
152
150
|
* @author Kasukabe Tsumugi <futami16237@gmail.com>
|
|
153
|
-
* @version 0.
|
|
151
|
+
* @version 0.16.1 (Last Update: 2026.01.28 08:51:19.714)
|
|
154
152
|
* @license MIT
|
|
155
153
|
* @link https://github.com/baendlorel/kt.js
|
|
156
154
|
* @link https://baendlorel.github.io/ Welcome to my site!
|
package/dist/jsx/jsx-runtime.mjs
CHANGED
|
@@ -203,7 +203,7 @@ let creator = defaultCreator;
|
|
|
203
203
|
* ## About
|
|
204
204
|
* @package @ktjs/core
|
|
205
205
|
* @author Kasukabe Tsumugi <futami16237@gmail.com>
|
|
206
|
-
* @version 0.
|
|
206
|
+
* @version 0.16.1 (Last Update: 2026.01.28 08:51:19.714)
|
|
207
207
|
* @license MIT
|
|
208
208
|
* @link https://github.com/baendlorel/kt.js
|
|
209
209
|
* @link https://baendlorel.github.io/ Welcome to my site!
|