@ktjs/core 0.27.2 → 0.28.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.
@@ -10,29 +10,6 @@ if (typeof Symbol === 'undefined') {
10
10
  };
11
11
  }
12
12
 
13
- // String manipulation utilities
14
- /**
15
- * Default empty function
16
- */
17
- const $emptyFn = (() => true);
18
- const $emptyArray = [];
19
- /**
20
- * Safe and quick forEach implementation that works with array-like objects and handles sparse arrays.
21
- */
22
- const $forEach = (array, callback) => {
23
- const len = array.length;
24
- for (let i = 0; i < len; i++) {
25
- callback(array[i], i, array);
26
- }
27
- };
28
-
29
- const $emptyChildrenRef = { value: $emptyArray };
30
- // each instance shares the same empty array, but it will be replaced when used
31
- Comment.prototype.kisFragmentAnchor = false;
32
- Comment.prototype.kFragmentList = $emptyArray;
33
- Comment.prototype.kredraw = $emptyFn;
34
- Comment.prototype.kchildrenRef = $emptyChildrenRef;
35
-
36
13
  // Shared constants
37
14
  // Empty for now - can be extended with framework-wide constants
38
15
  /**
@@ -102,9 +79,18 @@ const $applyModel = (element, valueRef, propName, eventName) => {
102
79
  valueRef.addOnChange((newValue) => (element[propName] = newValue));
103
80
  element.addEventListener(eventName, () => (valueRef.value = element[propName]));
104
81
  };
82
+ /**
83
+ * Safe and quick forEach implementation that works with array-like objects and handles sparse arrays.
84
+ */
85
+ const $forEach = (array, callback) => {
86
+ const len = array.length;
87
+ for (let i = 0; i < len; i++) {
88
+ callback(array[i], i, array);
89
+ }
90
+ };
105
91
 
106
92
  // incase that symbol is not supported
107
- Object.defineProperty(window, '__ktjs__', { value: '0.23.10' });
93
+ Object.defineProperty(window, '__ktjs__', { value: '0.23.11' });
108
94
 
109
95
  const isKT = (obj) => obj?.isKT;
110
96
  const isRef = (obj) => obj?.ktType === 1 /* KTReactiveType.REF */;
@@ -315,6 +301,14 @@ class KTRef {
315
301
  * @internal
316
302
  */
317
303
  _onChanges;
304
+ /**
305
+ * @internal
306
+ */
307
+ _emit(newValue, oldValue) {
308
+ for (let i = 0; i < this._onChanges.length; i++) {
309
+ this._onChanges[i](newValue, oldValue);
310
+ }
311
+ }
318
312
  constructor(_value, _onChanges) {
319
313
  this._value = _value;
320
314
  this._onChanges = _onChanges;
@@ -332,9 +326,30 @@ class KTRef {
332
326
  const oldValue = this._value;
333
327
  $replaceNode(oldValue, newValue);
334
328
  this._value = newValue;
335
- for (let i = 0; i < this._onChanges.length; i++) {
336
- this._onChanges[i](newValue, oldValue);
329
+ this._emit(newValue, oldValue);
330
+ }
331
+ /**
332
+ * Force all listeners to run even when reference identity has not changed.
333
+ * Useful for in-place array/object mutations.
334
+ */
335
+ notify() {
336
+ this._emit(this._value, this._value);
337
+ }
338
+ /**
339
+ * Mutate current value in-place and notify listeners once.
340
+ *
341
+ * @example
342
+ * const items = ref<number[]>([1, 2]);
343
+ * items.mutate((list) => list.push(3));
344
+ */
345
+ mutate(mutator) {
346
+ if (typeof mutator !== 'function') {
347
+ throw new Error('[kt.js error] KTRef.mutate: mutator must be a function');
337
348
  }
349
+ const oldValue = this._value;
350
+ const result = mutator(this._value);
351
+ this._emit(this._value, oldValue);
352
+ return result;
338
353
  }
339
354
  /**
340
355
  * Register a callback when the value changes
@@ -428,7 +443,7 @@ let creator = htmlCreator;
428
443
  * ## About
429
444
  * @package @ktjs/core
430
445
  * @author Kasukabe Tsumugi <futami16237@gmail.com>
431
- * @version 0.27.2 (Last Update: 2026.02.10 07:27:25.966)
446
+ * @version 0.28.1 (Last Update: 2026.02.10 11:23:01.128)
432
447
  * @license MIT
433
448
  * @link https://github.com/baendlorel/kt.js
434
449
  * @link https://baendlorel.github.io/ Welcome to my site!
@@ -463,34 +478,6 @@ const h = (tag, attr, content) => {
463
478
  return element;
464
479
  };
465
480
 
466
- const kredraw = function () {
467
- const newElements = this.kchildrenRef.value;
468
- const parent = this.parentNode;
469
- if (!parent) {
470
- // If anchor is not in DOM, only update internal state
471
- this.kFragmentList.length = 0;
472
- for (let i = 0; i < newElements.length; i++) {
473
- this.kFragmentList.push(newElements[i]);
474
- }
475
- return;
476
- }
477
- // Simple replacement algorithm: remove all old elements, insert all new elements
478
- // todo Future enhancement: key-based optimization
479
- // 1. Remove all old elements
480
- for (let i = 0; i < this.kFragmentList.length; i++) {
481
- this.kFragmentList[i].remove();
482
- }
483
- // 2. Insert all new elements
484
- const fragment = document.createDocumentFragment();
485
- this.kFragmentList.length = 0;
486
- for (let i = 0; i < newElements.length; i++) {
487
- const element = newElements[i];
488
- this.kFragmentList.push(element);
489
- fragment.appendChild(element);
490
- }
491
- // Insert after anchor
492
- parent.insertBefore(fragment, this.nextSibling);
493
- };
494
481
  /**
495
482
  * Fragment - Container component for managing arrays of child elements
496
483
  *
@@ -512,17 +499,44 @@ const kredraw = function () {
512
499
  */
513
500
  function Fragment$1(props) {
514
501
  // key parameter reserved for future enhancement, currently unused
515
- const { key: _key } = props;
516
- const childrenRef = toReactive(props.children, () => anchor.kredraw());
502
+ // const { key: _key } = props;
503
+ const redraw = () => {
504
+ const newElements = childrenRef.value;
505
+ const parent = anchor.parentNode;
506
+ if (!parent) {
507
+ // If anchor is not in DOM, only update internal state
508
+ elements.length = 0;
509
+ for (let i = 0; i < newElements.length; i++) {
510
+ elements.push(newElements[i]);
511
+ }
512
+ return;
513
+ }
514
+ // Simple replacement algorithm: remove all old elements, insert all new elements
515
+ // todo Future enhancement: key-based optimization
516
+ // 1. Remove all old elements
517
+ for (let i = 0; i < elements.length; i++) {
518
+ elements[i].remove();
519
+ }
520
+ // 2. Insert all new elements
521
+ const fragment = document.createDocumentFragment();
522
+ elements.length = 0;
523
+ for (let i = 0; i < newElements.length; i++) {
524
+ const element = newElements[i];
525
+ elements.push(element);
526
+ fragment.appendChild(element);
527
+ }
528
+ // Insert after anchor
529
+ parent.insertBefore(fragment, anchor.nextSibling);
530
+ };
531
+ let initialized = false;
532
+ const childrenRef = toReactive(props.children, redraw);
533
+ const elements = [];
517
534
  const anchor = document.createComment('kt-fragment');
518
- anchor.kredraw = kredraw;
519
- anchor.kchildrenRef = childrenRef;
520
- anchor.kFragmentList = [];
521
- anchor.kisFragmentAnchor = true;
522
535
  // Observe DOM insertion
523
536
  const observer = new MutationObserver(() => {
524
- if (anchor.isConnected) {
525
- anchor.kredraw();
537
+ if (anchor.isConnected && !initialized) {
538
+ initialized = true;
539
+ redraw();
526
540
  observer.disconnect();
527
541
  }
528
542
  });
@@ -4,6 +4,7 @@ declare const enum KTReactiveType {
4
4
  REF = 1,
5
5
  COMPUTED = 2
6
6
  }
7
+
7
8
  type ReactiveChangeHandler<T> = (newValue: T, oldValue: T) => void;
8
9
 
9
10
  declare class KTRef<T> {
@@ -18,6 +19,19 @@ declare class KTRef<T> {
18
19
  */
19
20
  get value(): T;
20
21
  set value(newValue: T);
22
+ /**
23
+ * Force all listeners to run even when reference identity has not changed.
24
+ * Useful for in-place array/object mutations.
25
+ */
26
+ notify(): void;
27
+ /**
28
+ * Mutate current value in-place and notify listeners once.
29
+ *
30
+ * @example
31
+ * const items = ref<number[]>([1, 2]);
32
+ * items.mutate((list) => list.push(3));
33
+ */
34
+ mutate<R = void>(mutator: (currentValue: T) => R): R;
21
35
  /**
22
36
  * Register a callback when the value changes
23
37
  * @param callback (newValue, oldValue) => xxx
@@ -134,7 +148,7 @@ type KTAttribute = KTBaseAttribute & KTPrefixedEventAttribute;
134
148
  * ## About
135
149
  * @package @ktjs/core
136
150
  * @author Kasukabe Tsumugi <futami16237@gmail.com>
137
- * @version 0.27.2 (Last Update: 2026.02.10 07:27:25.966)
151
+ * @version 0.28.1 (Last Update: 2026.02.10 11:23:01.128)
138
152
  * @license MIT
139
153
  * @link https://github.com/baendlorel/kt.js
140
154
  * @link https://baendlorel.github.io/ Welcome to my site!
@@ -10,29 +10,6 @@ if (typeof Symbol === 'undefined') {
10
10
  };
11
11
  }
12
12
 
13
- // String manipulation utilities
14
- /**
15
- * Default empty function
16
- */
17
- const $emptyFn = (() => true);
18
- const $emptyArray = [];
19
- /**
20
- * Safe and quick forEach implementation that works with array-like objects and handles sparse arrays.
21
- */
22
- const $forEach = (array, callback) => {
23
- const len = array.length;
24
- for (let i = 0; i < len; i++) {
25
- callback(array[i], i, array);
26
- }
27
- };
28
-
29
- const $emptyChildrenRef = { value: $emptyArray };
30
- // each instance shares the same empty array, but it will be replaced when used
31
- Comment.prototype.kisFragmentAnchor = false;
32
- Comment.prototype.kFragmentList = $emptyArray;
33
- Comment.prototype.kredraw = $emptyFn;
34
- Comment.prototype.kchildrenRef = $emptyChildrenRef;
35
-
36
13
  // Shared constants
37
14
  // Empty for now - can be extended with framework-wide constants
38
15
  /**
@@ -102,9 +79,18 @@ const $applyModel = (element, valueRef, propName, eventName) => {
102
79
  valueRef.addOnChange((newValue) => (element[propName] = newValue));
103
80
  element.addEventListener(eventName, () => (valueRef.value = element[propName]));
104
81
  };
82
+ /**
83
+ * Safe and quick forEach implementation that works with array-like objects and handles sparse arrays.
84
+ */
85
+ const $forEach = (array, callback) => {
86
+ const len = array.length;
87
+ for (let i = 0; i < len; i++) {
88
+ callback(array[i], i, array);
89
+ }
90
+ };
105
91
 
106
92
  // incase that symbol is not supported
107
- Object.defineProperty(window, '__ktjs__', { value: '0.23.10' });
93
+ Object.defineProperty(window, '__ktjs__', { value: '0.23.11' });
108
94
 
109
95
  const isKT = (obj) => obj?.isKT;
110
96
  const isRef = (obj) => obj?.ktType === 1 /* KTReactiveType.REF */;
@@ -315,6 +301,14 @@ class KTRef {
315
301
  * @internal
316
302
  */
317
303
  _onChanges;
304
+ /**
305
+ * @internal
306
+ */
307
+ _emit(newValue, oldValue) {
308
+ for (let i = 0; i < this._onChanges.length; i++) {
309
+ this._onChanges[i](newValue, oldValue);
310
+ }
311
+ }
318
312
  constructor(_value, _onChanges) {
319
313
  this._value = _value;
320
314
  this._onChanges = _onChanges;
@@ -332,9 +326,30 @@ class KTRef {
332
326
  const oldValue = this._value;
333
327
  $replaceNode(oldValue, newValue);
334
328
  this._value = newValue;
335
- for (let i = 0; i < this._onChanges.length; i++) {
336
- this._onChanges[i](newValue, oldValue);
329
+ this._emit(newValue, oldValue);
330
+ }
331
+ /**
332
+ * Force all listeners to run even when reference identity has not changed.
333
+ * Useful for in-place array/object mutations.
334
+ */
335
+ notify() {
336
+ this._emit(this._value, this._value);
337
+ }
338
+ /**
339
+ * Mutate current value in-place and notify listeners once.
340
+ *
341
+ * @example
342
+ * const items = ref<number[]>([1, 2]);
343
+ * items.mutate((list) => list.push(3));
344
+ */
345
+ mutate(mutator) {
346
+ if (typeof mutator !== 'function') {
347
+ throw new Error('[kt.js error] KTRef.mutate: mutator must be a function');
337
348
  }
349
+ const oldValue = this._value;
350
+ const result = mutator(this._value);
351
+ this._emit(this._value, oldValue);
352
+ return result;
338
353
  }
339
354
  /**
340
355
  * Register a callback when the value changes
@@ -428,7 +443,7 @@ let creator = htmlCreator;
428
443
  * ## About
429
444
  * @package @ktjs/core
430
445
  * @author Kasukabe Tsumugi <futami16237@gmail.com>
431
- * @version 0.27.2 (Last Update: 2026.02.10 07:27:25.966)
446
+ * @version 0.28.1 (Last Update: 2026.02.10 11:23:01.128)
432
447
  * @license MIT
433
448
  * @link https://github.com/baendlorel/kt.js
434
449
  * @link https://baendlorel.github.io/ Welcome to my site!
@@ -463,34 +478,6 @@ const h = (tag, attr, content) => {
463
478
  return element;
464
479
  };
465
480
 
466
- const kredraw = function () {
467
- const newElements = this.kchildrenRef.value;
468
- const parent = this.parentNode;
469
- if (!parent) {
470
- // If anchor is not in DOM, only update internal state
471
- this.kFragmentList.length = 0;
472
- for (let i = 0; i < newElements.length; i++) {
473
- this.kFragmentList.push(newElements[i]);
474
- }
475
- return;
476
- }
477
- // Simple replacement algorithm: remove all old elements, insert all new elements
478
- // todo Future enhancement: key-based optimization
479
- // 1. Remove all old elements
480
- for (let i = 0; i < this.kFragmentList.length; i++) {
481
- this.kFragmentList[i].remove();
482
- }
483
- // 2. Insert all new elements
484
- const fragment = document.createDocumentFragment();
485
- this.kFragmentList.length = 0;
486
- for (let i = 0; i < newElements.length; i++) {
487
- const element = newElements[i];
488
- this.kFragmentList.push(element);
489
- fragment.appendChild(element);
490
- }
491
- // Insert after anchor
492
- parent.insertBefore(fragment, this.nextSibling);
493
- };
494
481
  /**
495
482
  * Fragment - Container component for managing arrays of child elements
496
483
  *
@@ -512,17 +499,44 @@ const kredraw = function () {
512
499
  */
513
500
  function Fragment$1(props) {
514
501
  // key parameter reserved for future enhancement, currently unused
515
- const { key: _key } = props;
516
- const childrenRef = toReactive(props.children, () => anchor.kredraw());
502
+ // const { key: _key } = props;
503
+ const redraw = () => {
504
+ const newElements = childrenRef.value;
505
+ const parent = anchor.parentNode;
506
+ if (!parent) {
507
+ // If anchor is not in DOM, only update internal state
508
+ elements.length = 0;
509
+ for (let i = 0; i < newElements.length; i++) {
510
+ elements.push(newElements[i]);
511
+ }
512
+ return;
513
+ }
514
+ // Simple replacement algorithm: remove all old elements, insert all new elements
515
+ // todo Future enhancement: key-based optimization
516
+ // 1. Remove all old elements
517
+ for (let i = 0; i < elements.length; i++) {
518
+ elements[i].remove();
519
+ }
520
+ // 2. Insert all new elements
521
+ const fragment = document.createDocumentFragment();
522
+ elements.length = 0;
523
+ for (let i = 0; i < newElements.length; i++) {
524
+ const element = newElements[i];
525
+ elements.push(element);
526
+ fragment.appendChild(element);
527
+ }
528
+ // Insert after anchor
529
+ parent.insertBefore(fragment, anchor.nextSibling);
530
+ };
531
+ let initialized = false;
532
+ const childrenRef = toReactive(props.children, redraw);
533
+ const elements = [];
517
534
  const anchor = document.createComment('kt-fragment');
518
- anchor.kredraw = kredraw;
519
- anchor.kchildrenRef = childrenRef;
520
- anchor.kFragmentList = [];
521
- anchor.kisFragmentAnchor = true;
522
535
  // Observe DOM insertion
523
536
  const observer = new MutationObserver(() => {
524
- if (anchor.isConnected) {
525
- anchor.kredraw();
537
+ if (anchor.isConnected && !initialized) {
538
+ initialized = true;
539
+ redraw();
526
540
  observer.disconnect();
527
541
  }
528
542
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ktjs/core",
3
- "version": "0.27.2",
3
+ "version": "0.28.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",
@@ -44,7 +44,7 @@
44
44
  "directory": "packages/core"
45
45
  },
46
46
  "dependencies": {
47
- "@ktjs/shared": "0.23.10"
47
+ "@ktjs/shared": "0.23.11"
48
48
  },
49
49
  "scripts": {
50
50
  "build": "rollup -c rollup.config.mjs",