@ktjs/core 0.26.9 → 0.27.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/dist/index.mjs CHANGED
@@ -1,3 +1,39 @@
1
+ // Cached native methods for performance optimization
2
+ const $isArray = Array.isArray;
3
+ const $is = Object.is;
4
+ const $entries = Object.entries;
5
+ const $random = Math.random;
6
+ const $isThenable = (o) => typeof o?.then === 'function';
7
+
8
+ if (typeof Symbol === 'undefined') {
9
+ window.Symbol = function Symbol(description) {
10
+ return `@@SYMBOL_${description || ''}_${$random().toString(36).slice(2)}`;
11
+ };
12
+ }
13
+
14
+ // String manipulation utilities
15
+ /**
16
+ * Default empty function
17
+ */
18
+ const $emptyFn = (() => true);
19
+ const $emptyArray = [];
20
+ /**
21
+ * Safe and quick forEach implementation that works with array-like objects and handles sparse arrays.
22
+ */
23
+ const $forEach = (array, callback) => {
24
+ const len = array.length;
25
+ for (let i = 0; i < len; i++) {
26
+ callback(array[i], i, array);
27
+ }
28
+ };
29
+
30
+ const $emptyChildrenRef = { value: $emptyArray };
31
+ // each instance shares the same empty array, but it will be replaced when used
32
+ Comment.prototype.kisFragmentAnchor = false;
33
+ Comment.prototype.kFragmentList = $emptyArray;
34
+ Comment.prototype.kredraw = $emptyFn;
35
+ Comment.prototype.kchildrenRef = $emptyChildrenRef;
36
+
1
37
  // Shared constants
2
38
  // Empty for now - can be extended with framework-wide constants
3
39
  /**
@@ -9,13 +45,6 @@ const SVG_ATTR_FLAG = '__kt_svg__';
9
45
  */
10
46
  const MATHML_ATTR_FLAG = '__kt_mathml__';
11
47
 
12
- // Cached native methods for performance optimization
13
- const $isArray = Array.isArray;
14
- const $is = Object.is;
15
- const $entries = Object.entries;
16
- const $random = Math.random;
17
- const $isThenable = (o) => typeof o?.then === 'function';
18
-
19
48
  // DOM manipulation utilities
20
49
  // # dom natives
21
50
  const $isNode = (x) => x?.nodeType > 0;
@@ -69,26 +98,14 @@ const { get: $buttonDisabledGetter, set: $buttonDisabledSetter } = Object.getOwn
69
98
  /**
70
99
  * Used for `k-model`
71
100
  */
72
- const applyModel = (element, valueRef, propName, eventName) => {
101
+ const $applyModel = (element, valueRef, propName, eventName) => {
73
102
  element[propName] = valueRef.value; // initialize
74
103
  valueRef.addOnChange((newValue) => (element[propName] = newValue));
75
104
  element.addEventListener(eventName, () => (valueRef.value = element[propName]));
76
105
  };
77
106
 
78
- // String manipulation utilities
79
- /**
80
- * Default empty function
81
- */
82
- const $emptyFn = (() => true);
83
-
84
- if (typeof Symbol === 'undefined') {
85
- window.Symbol = function Symbol(description) {
86
- return `@@SYMBOL_${description || ''}_${$random().toString(36).slice(2)}`;
87
- };
88
- }
89
-
90
- // Shared utilities and cached native methods for kt.js framework
91
- Object.defineProperty(window, '__ktjs__', { value: '0.23.3' });
107
+ // incase that symbol is not supported
108
+ Object.defineProperty(window, '__ktjs__', { value: '0.23.10' });
92
109
 
93
110
  const isKT = (obj) => obj?.isKT;
94
111
  const isRef = (obj) => obj?.ktType === 1 /* KTReactiveType.REF */;
@@ -244,6 +261,7 @@ function apdSingle(element, c) {
244
261
  else {
245
262
  $append.call(element, c);
246
263
  // Handle KTFor anchor
264
+ // todo Maybe not needed anymore
247
265
  const list = c.__kt_for_list__;
248
266
  if ($isArray(list)) {
249
267
  apd(element, list);
@@ -402,13 +420,26 @@ const $modelOrRef = (props, defaultValue) => {
402
420
  // & props is an object. Won't use it in any other place
403
421
  if ('k-model' in props) {
404
422
  const kmodel = props['k-model'];
405
- if (!kmodel?.isKT) {
423
+ if (isRef(kmodel)) {
424
+ return kmodel;
425
+ }
426
+ else {
406
427
  throw new Error(`[kt.js error] k-model data must be a KTRef object, please use 'ref(...)' to wrap it.`);
407
428
  }
408
- return kmodel;
409
429
  }
410
430
  return ref(defaultValue);
411
431
  };
432
+ const $setRef = (props, node) => {
433
+ if ('ref' in props) {
434
+ const r = props.ref;
435
+ if (isRef(r)) {
436
+ r.value = node;
437
+ }
438
+ else {
439
+ throw new Error('[kt.js error] Fragment: ref must be a KTRef');
440
+ }
441
+ }
442
+ };
412
443
 
413
444
  class KTComputed {
414
445
  /**
@@ -561,17 +592,17 @@ function applyKModel(element, valueRef) {
561
592
  }
562
593
  if (element instanceof HTMLInputElement) {
563
594
  if (element.type === 'radio' || element.type === 'checkbox') {
564
- applyModel(element, valueRef, 'checked', 'change');
595
+ $applyModel(element, valueRef, 'checked', 'change');
565
596
  }
566
597
  else {
567
- applyModel(element, valueRef, 'value', 'input');
598
+ $applyModel(element, valueRef, 'value', 'input');
568
599
  }
569
600
  }
570
601
  else if (element instanceof HTMLSelectElement) {
571
- applyModel(element, valueRef, 'value', 'change');
602
+ $applyModel(element, valueRef, 'value', 'change');
572
603
  }
573
604
  else if (element instanceof HTMLTextAreaElement) {
574
- applyModel(element, valueRef, 'value', 'input');
605
+ $applyModel(element, valueRef, 'value', 'input');
575
606
  }
576
607
  else {
577
608
  console.warn('[kt.js warn]','not supported element for k-model:');
@@ -592,7 +623,7 @@ let creator = htmlCreator;
592
623
  * ## About
593
624
  * @package @ktjs/core
594
625
  * @author Kasukabe Tsumugi <futami16237@gmail.com>
595
- * @version 0.26.9 (Last Update: 2026.02.06 16:29:24.402)
626
+ * @version 0.27.1 (Last Update: 2026.02.10 01:30:30.991)
596
627
  * @license MIT
597
628
  * @link https://github.com/baendlorel/kt.js
598
629
  * @link https://baendlorel.github.io/ Welcome to my site!
@@ -627,7 +658,110 @@ const h = (tag, attr, content) => {
627
658
  return element;
628
659
  };
629
660
 
630
- const dummyRef = { value: null };
661
+ const kredraw = function () {
662
+ const newElements = this.kchildrenRef.value;
663
+ const parent = this.parentNode;
664
+ if (!parent) {
665
+ // If anchor is not in DOM, only update internal state
666
+ this.kFragmentList.length = 0;
667
+ for (let i = 0; i < newElements.length; i++) {
668
+ this.kFragmentList.push(newElements[i]);
669
+ }
670
+ return;
671
+ }
672
+ // Simple replacement algorithm: remove all old elements, insert all new elements
673
+ // todo Future enhancement: key-based optimization
674
+ // 1. Remove all old elements
675
+ for (let i = 0; i < this.kFragmentList.length; i++) {
676
+ this.kFragmentList[i].remove();
677
+ }
678
+ // 2. Insert all new elements
679
+ const fragment = document.createDocumentFragment();
680
+ this.kFragmentList.length = 0;
681
+ for (let i = 0; i < newElements.length; i++) {
682
+ const element = newElements[i];
683
+ this.kFragmentList.push(element);
684
+ fragment.appendChild(element);
685
+ }
686
+ // Insert after anchor
687
+ parent.insertBefore(fragment, this.nextSibling);
688
+ };
689
+ /**
690
+ * Fragment - Container component for managing arrays of child elements
691
+ *
692
+ * Features:
693
+ * 1. Returns a comment anchor node, child elements are inserted after the anchor
694
+ * 2. Supports reactive arrays, automatically updates DOM when array changes
695
+ * 3. Basic version uses simple replacement algorithm (remove all old elements, insert all new elements)
696
+ * 4. Future enhancement: key-based optimization
697
+ *
698
+ * Usage example:
699
+ * ```tsx
700
+ * const children = ref([<div>A</div>, <div>B</div>]);
701
+ * const fragment = <Fragment children={children} />;
702
+ * document.body.appendChild(fragment);
703
+ *
704
+ * // Automatic update
705
+ * children.value = [<div>C</div>, <div>D</div>];
706
+ * ```
707
+ */
708
+ function Fragment$1(props) {
709
+ // key parameter reserved for future enhancement, currently unused
710
+ const { key: _key } = props;
711
+ const childrenRef = toReactive(props.children, () => anchor.kredraw());
712
+ const anchor = document.createComment('kt-fragment');
713
+ anchor.kredraw = kredraw;
714
+ anchor.kchildrenRef = childrenRef;
715
+ anchor.kFragmentList = [];
716
+ anchor.kisFragmentAnchor = true;
717
+ // Observe DOM insertion
718
+ const observer = new MutationObserver(() => {
719
+ if (anchor.isConnected) {
720
+ anchor.kredraw();
721
+ observer.disconnect();
722
+ }
723
+ });
724
+ observer.observe(document.body, { childList: true, subtree: true });
725
+ // Set ref reference
726
+ $setRef(props, anchor);
727
+ return anchor;
728
+ }
729
+ /**
730
+ * Convert KTRawContent to HTMLElement array
731
+ */
732
+ function convertChildrenToElements(children) {
733
+ const elements = [];
734
+ const processChild = (child) => {
735
+ if (child == null || child === false || child === true) {
736
+ // Ignore null, undefined, false, true
737
+ return;
738
+ }
739
+ if ($isArray(child)) {
740
+ // Recursively process array
741
+ $forEach(child, processChild);
742
+ return;
743
+ }
744
+ if (typeof child === 'string' || typeof child === 'number') {
745
+ // & Wrap text in span element? No! use text node instead
746
+ const textNode = document.createTextNode(String(child));
747
+ elements.push(textNode);
748
+ return;
749
+ }
750
+ if (child instanceof HTMLElement) {
751
+ elements.push(child);
752
+ return;
753
+ }
754
+ if (isKT(child)) {
755
+ processChild(child.value);
756
+ return;
757
+ }
758
+ // Other types ignored or converted to string
759
+ console.warn('[kt.js warn]','Fragment: unsupported child type', child);
760
+ };
761
+ processChild(children);
762
+ return elements;
763
+ }
764
+
631
765
  const create = (tag, props) => {
632
766
  if (typeof tag === 'function') {
633
767
  return tag(props);
@@ -645,7 +779,6 @@ function jsx(tag, props) {
645
779
  if (isComputed(props.ref)) {
646
780
  throw new Error('[kt.js error] Cannot assign a computed value to an element.');
647
781
  }
648
- const maybeDummyRef = isRef(props.ref) ? props.ref : dummyRef;
649
782
  let el;
650
783
  if ('k-if' in props) {
651
784
  const kif = props['k-if'];
@@ -657,49 +790,31 @@ function jsx(tag, props) {
657
790
  return;
658
791
  }
659
792
  const oldEl = el;
660
- el = newValue ? create(tag, props) : placeholder();
793
+ $setRef(props, (el = newValue ? create(tag, props) : placeholder()));
661
794
  $replaceNode(oldEl, el);
662
- maybeDummyRef.value = el;
663
795
  });
664
796
  condition = kif.value;
665
797
  }
666
798
  if (!condition) {
667
799
  // & make comment placeholder in case that ref might be redrawn later
668
- el = placeholder();
669
- maybeDummyRef.value = el;
800
+ $setRef(props, (el = placeholder()));
670
801
  return el;
671
802
  }
672
803
  }
673
- el = create(tag, props);
674
- maybeDummyRef.value = el;
804
+ $setRef(props, (el = create(tag, props)));
675
805
  return el;
676
806
  }
677
807
  /**
678
808
  * Fragment support - returns an array of children
679
- * Note: kt.js doesn't have a real Fragment concept,
809
+ * Enhanced Fragment component that manages arrays of elements
680
810
  */
681
- function Fragment(_props) {
682
- throw new Error("[kt.js error] doesn't have a Fragment concept");
683
- // const { children } = props ?? {};
684
- // if (!children) {
685
- // return ;
686
- // }
687
- // // If single child, return it directly
688
- // if (!Array.isArray(children)) {
689
- // return children as HTMLElement;
690
- // }
691
- // // For multiple children, create a document fragment wrapper
692
- // // This is a limitation - JSX fragments must be wrapped in kt.js
693
- // const wrapper = document.createElement('div');
694
- // wrapper.setAttribute('data-kt-fragment', 'true');
695
- // children.forEach((child) => {
696
- // if (typeof child === 'string') {
697
- // wrapper.appendChild(document.createTextNode(child));
698
- // } else if (child instanceof HTMLElement) {
699
- // wrapper.appendChild(child);
700
- // }
701
- // });
702
- // return wrapper;
811
+ function Fragment(props) {
812
+ const { children } = props ?? {};
813
+ if (!children) {
814
+ return document.createComment('kt-fragment-empty');
815
+ }
816
+ const elements = convertChildrenToElements(children);
817
+ return Fragment$1({ children: elements });
703
818
  }
704
819
  /**
705
820
  * JSX Development runtime - same as jsx but with additional dev checks
@@ -756,45 +871,17 @@ function KTAsync(props) {
756
871
  * Returns a Comment anchor node with rendered elements in __kt_for_list__
757
872
  */
758
873
  function KTFor(props) {
759
- const { list, map } = props;
760
- const key = props.key ?? ((item) => item);
761
- // Create anchor comment node
762
- const anchor = document.createComment('kt-for');
763
- // Store current state
764
- let currentList = list;
765
- let currentKey = key;
766
- let currentMap = map;
767
- // Map to track rendered nodes by key
768
- const nodeMap = new Map();
769
- // Render initial list
770
- const elements = [];
771
- for (let index = 0; index < currentList.length; index++) {
772
- const item = currentList[index];
773
- const itemKey = currentKey(item, index, currentList);
774
- const node = currentMap(item, index, currentList);
775
- nodeMap.set(itemKey, node);
776
- elements.push(node);
777
- }
778
- // Attach elements array to anchor
779
- anchor.__kt_for_list__ = elements;
780
- // Redraw function for updates
781
- anchor.redraw = (newProps) => {
782
- const newList = (newProps?.list ?? currentList);
783
- const newKey = (newProps?.key ?? currentKey);
784
- const newMap = (newProps?.map ?? currentMap);
785
- // Update stored values
786
- currentList = newList;
787
- currentKey = newKey;
788
- currentMap = newMap;
874
+ const redraw = () => {
875
+ const newList = listRef.value;
789
876
  const parent = anchor.parentNode;
790
877
  if (!parent) {
791
878
  // If not in DOM yet, just rebuild the list
792
879
  const newElements = [];
793
880
  nodeMap.clear();
794
- for (let index = 0; index < currentList.length; index++) {
795
- const item = currentList[index];
796
- const itemKey = currentKey(item, index, currentList);
797
- const node = currentMap(item, index, currentList);
881
+ for (let index = 0; index < newList.length; index++) {
882
+ const item = newList[index];
883
+ const itemKey = currentKey(item, index, newList);
884
+ const node = currentMap(item, index, newList);
798
885
  nodeMap.set(itemKey, node);
799
886
  newElements.push(node);
800
887
  }
@@ -816,8 +903,8 @@ function KTFor(props) {
816
903
  const fragment = document.createDocumentFragment();
817
904
  for (let i = 0; i < newLength; i++) {
818
905
  const item = newList[i];
819
- const itemKey = newKey(item, i, newList);
820
- const node = newMap(item, i, newList);
906
+ const itemKey = currentKey(item, i, newList);
907
+ const node = currentMap(item, i, newList);
821
908
  nodeMap.set(itemKey, node);
822
909
  newElements.push(node);
823
910
  fragment.appendChild(node);
@@ -833,7 +920,7 @@ function KTFor(props) {
833
920
  let moved = false;
834
921
  for (let i = 0; i < newLength; i++) {
835
922
  const item = newList[i];
836
- const itemKey = newKey(item, i, newList);
923
+ const itemKey = currentKey(item, i, newList);
837
924
  newKeyToNewIndex.set(itemKey, i);
838
925
  if (nodeMap.has(itemKey)) {
839
926
  // Reuse existing node
@@ -849,7 +936,7 @@ function KTFor(props) {
849
936
  }
850
937
  else {
851
938
  // Create new node
852
- newElements[i] = newMap(item, i, newList);
939
+ newElements[i] = currentMap(item, i, newList);
853
940
  }
854
941
  }
855
942
  // Remove nodes not in new list
@@ -865,7 +952,7 @@ function KTFor(props) {
865
952
  // Update DOM with minimal operations
866
953
  if (moved) {
867
954
  // Use longest increasing subsequence to minimize moves
868
- const seq = getSequence(newElements.map((el, i) => (nodeMap.has(newKey(newList[i], i, newList)) ? i : -1)));
955
+ const seq = getSequence(newElements.map((el, i) => (nodeMap.has(currentKey(newList[i], i, newList)) ? i : -1)));
869
956
  let j = seq.length - 1;
870
957
  let anchor = null;
871
958
  // Traverse from end to start for stable insertions
@@ -908,16 +995,28 @@ function KTFor(props) {
908
995
  // Update maps
909
996
  nodeMap.clear();
910
997
  for (let i = 0; i < newLength; i++) {
911
- const itemKey = newKey(newList[i], i, newList);
998
+ const itemKey = currentKey(newList[i], i, newList);
912
999
  nodeMap.set(itemKey, newElements[i]);
913
1000
  }
914
1001
  anchor.__kt_for_list__ = newElements;
915
1002
  return anchor;
916
1003
  };
917
- // Set ref if provided
918
- if (isRef(props.ref)) {
919
- props.ref.value = anchor;
1004
+ const { key: currentKey = (item) => item, map: currentMap } = props;
1005
+ const listRef = toReactive(props.list, redraw);
1006
+ const anchor = document.createComment('kt-for');
1007
+ // Map to track rendered nodes by key
1008
+ const nodeMap = new Map();
1009
+ // Render initial list
1010
+ const elements = [];
1011
+ for (let index = 0; index < listRef.value.length; index++) {
1012
+ const item = listRef.value[index];
1013
+ const itemKey = currentKey(item, index, listRef.value);
1014
+ const node = currentMap(item, index, listRef.value);
1015
+ nodeMap.set(itemKey, node);
1016
+ elements.push(node);
920
1017
  }
1018
+ anchor.__kt_for_list__ = elements;
1019
+ $setRef(props, anchor);
921
1020
  return anchor;
922
1021
  }
923
1022
  // Longest Increasing Subsequence algorithm (optimized for diff)
@@ -963,4 +1062,4 @@ function getSequence(arr) {
963
1062
  return result;
964
1063
  }
965
1064
 
966
- export { $modelOrRef, Fragment, KTAsync, KTComputed, KTFor, KTRef, computed, h as createElement, createRedrawable, deref, effect, h, isComputed, isKT, isRef, jsx, jsxDEV, jsxs, ref, surfaceRef, toReactive, toRef };
1065
+ export { $modelOrRef, $setRef, Fragment, KTAsync, KTComputed, KTFor, KTRef, computed, h as createElement, createRedrawable, deref, effect, h, isComputed, isKT, isRef, jsx, jsxDEV, jsxs, ref, surfaceRef, toReactive, toRef };
@@ -164,7 +164,7 @@ type KTAttribute = KTBaseAttribute & KTPrefixedEventAttribute;
164
164
  * ## About
165
165
  * @package @ktjs/core
166
166
  * @author Kasukabe Tsumugi <futami16237@gmail.com>
167
- * @version 0.26.9 (Last Update: 2026.02.06 16:29:24.402)
167
+ * @version 0.27.1 (Last Update: 2026.02.10 01:30:30.991)
168
168
  * @license MIT
169
169
  * @link https://github.com/baendlorel/kt.js
170
170
  * @link https://baendlorel.github.io/ Welcome to my site!
@@ -181,11 +181,11 @@ type JSXTag = HTMLTag | ((props?: any) => HTMLElement) | ((props?: any) => Promi
181
181
  declare function jsx(tag: JSXTag, props: KTAttribute): JSX.Element;
182
182
  /**
183
183
  * Fragment support - returns an array of children
184
- * Note: kt.js doesn't have a real Fragment concept,
184
+ * Enhanced Fragment component that manages arrays of elements
185
185
  */
186
- declare function Fragment(_props: {
186
+ declare function Fragment(props: {
187
187
  children?: KTRawContent;
188
- }): HTMLElement;
188
+ }): JSX.Element;
189
189
  /**
190
190
  * JSX Development runtime - same as jsx but with additional dev checks
191
191
  */