@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 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.14.6
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.15.6 (Last Update: 2026.01.27 15:41:57.932)
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
- export { Fragment, KTAsync, h as createElement, createRedrawable, createRedrawableNoref, h, jsx, jsxDEV, jsxs, ref };
248
- export type { EventHandler, HTMLTag, KTAttribute, KTHTMLElement, KTRawAttr, KTRawContent, KTRawContents, KTRef };
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 };
@@ -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.15.6 (Last Update: 2026.01.27 15:41:57.932)
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;
@@ -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.15.6 (Last Update: 2026.01.27 15:41:57.932)
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.15.6 (Last Update: 2026.01.27 15:41:57.932)
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
- export { Fragment, KTAsync, h as createElement, createRedrawable, createRedrawableNoref, h, jsx, jsxDEV, jsxs, ref };
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 };
@@ -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.15.6 (Last Update: 2026.01.27 15:41:57.932)
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!
@@ -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.15.6 (Last Update: 2026.01.27 15:41:57.932)
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.15.6 (Last Update: 2026.01.27 15:41:57.932)
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!
@@ -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.15.6 (Last Update: 2026.01.27 15:41:57.932)
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!
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ktjs/core",
3
- "version": "0.15.6",
3
+ "version": "0.16.1",
4
4
  "description": "Core functionality for kt.js - DOM manipulation utilities with JSX/TSX support",
5
5
  "type": "module",
6
6
  "module": "./dist/index.mjs",