@ktjs/core 0.28.2 → 0.29.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.
@@ -1,1144 +0,0 @@
1
- var __ktjs_core__ = (function (exports) {
2
- 'use strict';
3
-
4
- // Cached native methods for performance optimization
5
- const $isArray = Array.isArray;
6
- const $is = Object.is;
7
- const $entries = Object.entries;
8
- const $random = Math.random;
9
- const $isThenable = (o) => typeof o?.then === 'function';
10
-
11
- if (typeof Symbol === 'undefined') {
12
- window.Symbol = function Symbol(description) {
13
- return `@@SYMBOL_${description || ''}_${$random().toString(36).slice(2)}`;
14
- };
15
- }
16
-
17
- // Shared constants
18
- // Empty for now - can be extended with framework-wide constants
19
- /**
20
- * Mark the attribute as SVG to handle special cases during rendering.
21
- */
22
- const SVG_ATTR_FLAG = '__kt_svg__';
23
- /**
24
- * Mark the attribute as MathML to handle special cases during rendering.
25
- */
26
- const MATHML_ATTR_FLAG = '__kt_mathml__';
27
-
28
- // DOM manipulation utilities
29
- // # dom natives
30
- const $isNode = (x) => x?.nodeType > 0;
31
- /**
32
- * Safe replace `oldNode` With `newNode`
33
- */
34
- const $replaceNode = (oldNode, newNode) => {
35
- if ($isNode(oldNode) && $isNode(newNode)) {
36
- if (newNode.contains(oldNode)) {
37
- newNode.remove();
38
- }
39
- oldNode.replaceWith(newNode);
40
- }
41
- };
42
- /**
43
- * & Remove `bind` because it is shockingly slower than wrapper
44
- * & `window.document` is safe because it is not configurable and its setter is undefined
45
- */
46
- const $appendChild = HTMLElement.prototype.appendChild;
47
- const originAppend = HTMLElement.prototype.append;
48
- const $append = // for ie 9/10/11
49
- typeof originAppend === 'function'
50
- ? originAppend
51
- : function (...nodes) {
52
- if (nodes.length < 50) {
53
- for (let i = 0; i < nodes.length; i++) {
54
- const node = nodes[i];
55
- if (typeof node === 'string') {
56
- $appendChild.call(this, document.createTextNode(node));
57
- }
58
- else {
59
- $appendChild.call(this, node);
60
- }
61
- }
62
- }
63
- else {
64
- const fragment = document.createDocumentFragment();
65
- for (let i = 0; i < nodes.length; i++) {
66
- const node = nodes[i];
67
- if (typeof node === 'string') {
68
- $appendChild.call(fragment, document.createTextNode(node));
69
- }
70
- else {
71
- $appendChild.call(fragment, node);
72
- }
73
- }
74
- $appendChild.call(this, fragment);
75
- }
76
- };
77
- const { get: $buttonDisabledGetter, set: $buttonDisabledSetter } = Object.getOwnPropertyDescriptor(HTMLButtonElement.prototype, 'disabled');
78
- /**
79
- * Used for `k-model`
80
- */
81
- const $applyModel = (element, valueRef, propName, eventName) => {
82
- element[propName] = valueRef.value; // initialize
83
- valueRef.addOnChange((newValue) => (element[propName] = newValue));
84
- element.addEventListener(eventName, () => (valueRef.value = element[propName]));
85
- };
86
-
87
- // String manipulation utilities
88
- /**
89
- * Default empty function
90
- */
91
- const $emptyFn = (() => true);
92
- /**
93
- * Safe and quick forEach implementation that works with array-like objects and handles sparse arrays.
94
- */
95
- const $forEach = (array, callback) => {
96
- const len = array.length;
97
- for (let i = 0; i < len; i++) {
98
- callback(array[i], i, array);
99
- }
100
- };
101
-
102
- // incase that symbol is not supported
103
- Object.defineProperty(window, '__ktjs__', { value: '0.23.11' });
104
-
105
- const isKT = (obj) => obj?.isKT;
106
- const isRef = (obj) => obj?.ktType === 1 /* KTReactiveType.REF */;
107
- const isComputed = (obj) => obj?.ktType === 2 /* KTReactiveType.COMPUTED */;
108
-
109
- const booleanHandler = (element, key, value) => {
110
- if (key in element) {
111
- element[key] = !!value;
112
- }
113
- else {
114
- element.setAttribute(key, value);
115
- }
116
- };
117
- const valueHandler = (element, key, value) => {
118
- if (key in element) {
119
- element[key] = value;
120
- }
121
- else {
122
- element.setAttribute(key, value);
123
- }
124
- };
125
- // Attribute handlers map for optimized lookup
126
- const handlers = {
127
- checked: booleanHandler,
128
- selected: booleanHandler,
129
- value: valueHandler,
130
- valueAsDate: valueHandler,
131
- valueAsNumber: valueHandler,
132
- defaultValue: valueHandler,
133
- defaultChecked: booleanHandler,
134
- defaultSelected: booleanHandler,
135
- disabled: booleanHandler,
136
- readOnly: booleanHandler,
137
- multiple: booleanHandler,
138
- required: booleanHandler,
139
- autofocus: booleanHandler,
140
- open: booleanHandler,
141
- controls: booleanHandler,
142
- autoplay: booleanHandler,
143
- loop: booleanHandler,
144
- muted: booleanHandler,
145
- defer: booleanHandler,
146
- async: booleanHandler,
147
- hidden: (element, _key, value) => (element.hidden = !!value),
148
- };
149
-
150
- const defaultHandler = (element, key, value) => element.setAttribute(key, value);
151
- const setElementStyle = (element, style) => {
152
- if (typeof style === 'string') {
153
- element.style.cssText = style;
154
- return;
155
- }
156
- for (const key in style) {
157
- element.style[key] = style[key];
158
- }
159
- };
160
- function attrIsObject(element, attr) {
161
- const classValue = attr.class || attr.className;
162
- if (classValue !== undefined) {
163
- if (isKT(classValue)) {
164
- element.setAttribute('class', classValue.value);
165
- classValue.addOnChange((v) => element.setAttribute('class', v));
166
- }
167
- else {
168
- element.setAttribute('class', classValue);
169
- }
170
- }
171
- const style = attr.style;
172
- if (style) {
173
- if (typeof style === 'string') {
174
- element.setAttribute('style', style);
175
- }
176
- else if (typeof style === 'object') {
177
- if (isKT(style)) {
178
- setElementStyle(element, style.value);
179
- style.addOnChange((v) => setElementStyle(element, v));
180
- }
181
- else {
182
- setElementStyle(element, style);
183
- }
184
- }
185
- }
186
- if ('k-html' in attr) {
187
- const html = attr['k-html'];
188
- if (isKT(html)) {
189
- element.innerHTML = html.value;
190
- html.addOnChange((v) => (element.innerHTML = v));
191
- }
192
- else {
193
- element.innerHTML = html;
194
- }
195
- }
196
- for (const key in attr) {
197
- if (key === 'k-if' ||
198
- key === 'k-model' ||
199
- key === 'ref' ||
200
- key === 'class' ||
201
- key === 'className' ||
202
- key === 'style' ||
203
- key === 'children' ||
204
- key === 'k-html') {
205
- continue;
206
- }
207
- const o = attr[key];
208
- // normal event handler
209
- if (key.startsWith('on:')) {
210
- element.addEventListener(key.slice(3), o); // chop off the `on:`
211
- }
212
- // normal attributes
213
- else {
214
- const handler = handlers[key] || defaultHandler;
215
- if (isKT(o)) {
216
- handler(element, key, o.value);
217
- o.addOnChange((v) => handler(element, key, v));
218
- }
219
- else {
220
- handler(element, key, o);
221
- }
222
- }
223
- }
224
- }
225
- function applyAttr(element, attr) {
226
- if (!attr) {
227
- return;
228
- }
229
- if (typeof attr === 'object' && attr !== null) {
230
- attrIsObject(element, attr);
231
- }
232
- else {
233
- throw new Error('[kt.js error] attr must be an object.');
234
- }
235
- }
236
-
237
- const assureNode = (o) => ($isNode(o) ? o : document.createTextNode(o));
238
- function apdSingle(element, c) {
239
- // & JSX should ignore false, undefined, and null
240
- if (c === false || c === undefined || c === null) {
241
- return;
242
- }
243
- if (isKT(c)) {
244
- let node = assureNode(c.value);
245
- $append.call(element, node);
246
- c.addOnChange((newValue, oldValue) => {
247
- if ($isNode(newValue) && $isNode(oldValue)) {
248
- // & this case is handled automically in `class KTRef`
249
- return;
250
- }
251
- const oldNode = node;
252
- node = assureNode(newValue);
253
- oldNode.replaceWith(node);
254
- });
255
- }
256
- else {
257
- $append.call(element, c);
258
- // Handle KTFor anchor
259
- // todo Maybe not needed anymore
260
- const list = c.__kt_for_list__;
261
- if ($isArray(list)) {
262
- apd(element, list);
263
- }
264
- }
265
- }
266
- function apd(element, c) {
267
- if ($isThenable(c)) {
268
- c.then((r) => apd(element, r));
269
- }
270
- else if ($isArray(c)) {
271
- for (let i = 0; i < c.length; i++) {
272
- // & might be thenable here too
273
- const ci = c[i];
274
- if ($isThenable(ci)) {
275
- const comment = document.createComment('ktjs-promise-placeholder');
276
- $append.call(element, comment);
277
- ci.then((awaited) => comment.replaceWith(awaited));
278
- }
279
- else {
280
- apdSingle(element, ci);
281
- }
282
- }
283
- }
284
- else {
285
- // & here is thened, so must be a simple elementj
286
- apdSingle(element, c);
287
- }
288
- }
289
- function applyContent(element, content) {
290
- if ($isArray(content)) {
291
- for (let i = 0; i < content.length; i++) {
292
- apd(element, content[i]);
293
- }
294
- }
295
- else {
296
- apd(element, content);
297
- }
298
- }
299
-
300
- class KTRef {
301
- /**
302
- * Indicates that this is a KTRef instance
303
- */
304
- isKT = true;
305
- ktType = 1 /* KTReactiveType.REF */;
306
- /**
307
- * @internal
308
- */
309
- _value;
310
- /**
311
- * @internal
312
- */
313
- _onChanges;
314
- /**
315
- * @internal
316
- */
317
- _emit(newValue, oldValue) {
318
- for (let i = 0; i < this._onChanges.length; i++) {
319
- this._onChanges[i](newValue, oldValue);
320
- }
321
- }
322
- constructor(_value, _onChanges) {
323
- this._value = _value;
324
- this._onChanges = _onChanges;
325
- }
326
- /**
327
- * If new value and old value are both nodes, the old one will be replaced in the DOM
328
- */
329
- get value() {
330
- return this._value;
331
- }
332
- set value(newValue) {
333
- if ($is(newValue, this._value)) {
334
- return;
335
- }
336
- const oldValue = this._value;
337
- $replaceNode(oldValue, newValue);
338
- this._value = newValue;
339
- this._emit(newValue, oldValue);
340
- }
341
- /**
342
- * Force all listeners to run even when reference identity has not changed.
343
- * Useful for in-place array/object mutations.
344
- */
345
- notify() {
346
- this._emit(this._value, this._value);
347
- }
348
- /**
349
- * Mutate current value in-place and notify listeners once.
350
- *
351
- * @example
352
- * const items = ref<number[]>([1, 2]);
353
- * items.mutate((list) => list.push(3));
354
- */
355
- mutate(mutator) {
356
- if (typeof mutator !== 'function') {
357
- throw new Error('[kt.js error] KTRef.mutate: mutator must be a function');
358
- }
359
- const oldValue = this._value;
360
- const result = mutator(this._value);
361
- this._emit(this._value, oldValue);
362
- return result;
363
- }
364
- /**
365
- * Register a callback when the value changes
366
- * @param callback (newValue, oldValue) => xxx
367
- */
368
- addOnChange(callback) {
369
- if (typeof callback !== 'function') {
370
- throw new Error('[kt.js error] KTRef.addOnChange: callback must be a function');
371
- }
372
- this._onChanges.push(callback);
373
- }
374
- removeOnChange(callback) {
375
- for (let i = this._onChanges.length - 1; i >= 0; i--) {
376
- if (this._onChanges[i] === callback) {
377
- this._onChanges.splice(i, 1);
378
- return true;
379
- }
380
- }
381
- return false;
382
- }
383
- }
384
- /**
385
- * Reference to the created HTML element.
386
- * - **Only** respond to `ref.value` changes, not reactive to internal changes of the element.
387
- * - can alse be used to store normal values, but it is not reactive.
388
- * - if the value is already a `KTRef`, it will be returned **directly**.
389
- * @param value mostly an HTMLElement
390
- */
391
- function ref(value, onChange) {
392
- return new KTRef(value, onChange ? [onChange] : []);
393
- }
394
- /**
395
- * Convert a value to `KTRef`.
396
- * - Returns the original value if it is already a `KTRef`.
397
- * - Throws error if the value is a `KTComputed`.
398
- * - Otherwise wraps the value with `ref()`.
399
- * @param o value to convert
400
- */
401
- const toRef = (o) => {
402
- if (isRef(o)) {
403
- return o;
404
- }
405
- else if (isComputed(o)) {
406
- throw new Error('[kt.js error] Computed values cannot be used as KTRef.');
407
- }
408
- else {
409
- return ref(o);
410
- }
411
- };
412
- function kcollect() {
413
- const newObj = {};
414
- const entries = $entries(this);
415
- for (let i = 0; i < entries.length; i++) {
416
- const key = entries[i][0];
417
- if (key === 'kcollect') {
418
- continue;
419
- }
420
- newObj[key] = entries[i][1].value;
421
- }
422
- return newObj;
423
- }
424
- /**
425
- * Make all first-level properties of the object a `KTRef`.
426
- * - `obj.a.b` is not reactive
427
- */
428
- const surfaceRef = (obj) => {
429
- const entries = $entries(obj);
430
- const newObj = { kcollect };
431
- for (let i = 0; i < entries.length; i++) {
432
- newObj[entries[i][0]] = ref(entries[i][1]);
433
- }
434
- return newObj;
435
- };
436
- // # asserts
437
- /**
438
- * Assert k-model to be a ref object
439
- */
440
- const $modelOrRef = (props, defaultValue) => {
441
- // & props is an object. Won't use it in any other place
442
- if ('k-model' in props) {
443
- const kmodel = props['k-model'];
444
- if (isRef(kmodel)) {
445
- return kmodel;
446
- }
447
- else {
448
- throw new Error(`[kt.js error] k-model data must be a KTRef object, please use 'ref(...)' to wrap it.`);
449
- }
450
- }
451
- return ref(defaultValue);
452
- };
453
- const $setRef = (props, node) => {
454
- if ('ref' in props) {
455
- const r = props.ref;
456
- if (isRef(r)) {
457
- r.value = node;
458
- }
459
- else {
460
- throw new Error('[kt.js error] Fragment: ref must be a KTRef');
461
- }
462
- }
463
- };
464
-
465
- class KTComputed {
466
- /**
467
- * Indicates that this is a KTRef instance
468
- */
469
- isKT = true;
470
- ktType = 2 /* KTReactiveType.COMPUTED */;
471
- /**
472
- * @internal
473
- */
474
- _calculator;
475
- /**
476
- * @internal
477
- */
478
- _value;
479
- /**
480
- * @internal
481
- */
482
- _onChanges = [];
483
- /**
484
- * @internal
485
- */
486
- _emit(newValue, oldValue) {
487
- for (let i = 0; i < this._onChanges.length; i++) {
488
- this._onChanges[i](newValue, oldValue);
489
- }
490
- }
491
- /**
492
- * @internal
493
- */
494
- _recalculate(forceEmit = false) {
495
- const oldValue = this._value;
496
- const newValue = this._calculator();
497
- if (oldValue === newValue) {
498
- if (forceEmit) {
499
- this._emit(newValue, oldValue);
500
- }
501
- return;
502
- }
503
- this._value = newValue;
504
- $replaceNode(oldValue, newValue);
505
- this._emit(newValue, oldValue);
506
- }
507
- /**
508
- * @internal
509
- */
510
- _subscribe(reactives) {
511
- for (let i = 0; i < reactives.length; i++) {
512
- const reactive = reactives[i];
513
- reactive.addOnChange(() => this._recalculate());
514
- }
515
- }
516
- constructor(_calculator, reactives) {
517
- this._calculator = _calculator;
518
- this._value = _calculator();
519
- this._subscribe(reactives);
520
- }
521
- /**
522
- * If new value and old value are both nodes, the old one will be replaced in the DOM
523
- */
524
- get value() {
525
- return this._value;
526
- }
527
- set value(_newValue) {
528
- throw new Error('[kt.js error] KTComputed: cannot set value of a computed value');
529
- }
530
- /**
531
- * Force listeners to run once with the latest computed result.
532
- */
533
- notify() {
534
- this._recalculate(true);
535
- }
536
- /**
537
- * Computed values are derived from dependencies and should not be mutated manually.
538
- */
539
- mutate() {
540
- console.warn('[kt.js warn]','KTComputed.mutate: computed is derived automatically; manual mutate is ignored. Use notify() instead');
541
- return this._value;
542
- }
543
- /**
544
- * Register a callback when the value changes
545
- * @param callback (newValue, oldValue) => xxx
546
- */
547
- addOnChange(callback) {
548
- if (typeof callback !== 'function') {
549
- throw new Error('[kt.js error] KTRef.addOnChange: callback must be a function');
550
- }
551
- this._onChanges.push(callback);
552
- }
553
- /**
554
- * Unregister a callback
555
- * @param callback (newValue, oldValue) => xxx
556
- */
557
- removeOnChange(callback) {
558
- for (let i = this._onChanges.length - 1; i >= 0; i--) {
559
- if (this._onChanges[i] === callback) {
560
- this._onChanges.splice(i, 1);
561
- return true;
562
- }
563
- }
564
- return false;
565
- }
566
- }
567
- /**
568
- * Create a reactive computed value
569
- * @param computeFn
570
- * @param reactives refs and computeds that this computed depends on
571
- */
572
- function computed(computeFn, reactives) {
573
- if (reactives.some((v) => !isKT(v))) {
574
- throw new Error('[kt.js error] computed: all reactives must be KTRef or KTComputed instances');
575
- }
576
- return new KTComputed(computeFn, reactives);
577
- }
578
-
579
- /**
580
- * Register a reactive effect with options.
581
- * @param effectFn The effect function to run when dependencies change
582
- * @param reactives The reactive dependencies
583
- * @param options Effect options: lazy, onCleanup, debugName
584
- * @returns stop function to remove all listeners
585
- */
586
- function effect(effectFn, reactives, options) {
587
- const { lazy = false, onCleanup = $emptyFn, debugName = '' } = Object(options);
588
- let active = true;
589
- const run = () => {
590
- if (!active) {
591
- return;
592
- }
593
- // cleanup before rerun
594
- onCleanup();
595
- try {
596
- effectFn();
597
- }
598
- catch (err) {
599
- console.debug('[kt.js debug]','effect error:', debugName, err);
600
- }
601
- };
602
- // subscribe to dependencies
603
- for (let i = 0; i < reactives.length; i++) {
604
- reactives[i].addOnChange(run);
605
- }
606
- // auto run unless lazy
607
- if (!lazy) {
608
- run();
609
- }
610
- // stop function
611
- return () => {
612
- if (!active) {
613
- return;
614
- }
615
- active = false;
616
- for (let i = 0; i < reactives.length; i++) {
617
- reactives[i].removeOnChange(run);
618
- }
619
- // final cleanup
620
- onCleanup();
621
- };
622
- }
623
-
624
- const toReactive = (value, onChange) => {
625
- if (isKT(value)) {
626
- if (onChange) {
627
- value.addOnChange(onChange);
628
- }
629
- return value;
630
- }
631
- else {
632
- return ref(value, onChange);
633
- }
634
- };
635
- /**
636
- * Extracts the value from a KTReactive, or returns the value directly if it's not reactive.
637
- */
638
- function dereactive(value) {
639
- return isKT(value) ? value.value : value;
640
- }
641
-
642
- function applyKModel(element, valueRef) {
643
- if (!isKT(valueRef)) {
644
- console.warn('[kt.js warn]','k-model value must be a KTRef.');
645
- return;
646
- }
647
- if (element instanceof HTMLInputElement) {
648
- if (element.type === 'radio' || element.type === 'checkbox') {
649
- $applyModel(element, valueRef, 'checked', 'change');
650
- }
651
- else {
652
- $applyModel(element, valueRef, 'value', 'input');
653
- }
654
- }
655
- else if (element instanceof HTMLSelectElement) {
656
- $applyModel(element, valueRef, 'value', 'change');
657
- }
658
- else if (element instanceof HTMLTextAreaElement) {
659
- $applyModel(element, valueRef, 'value', 'input');
660
- }
661
- else {
662
- console.warn('[kt.js warn]','not supported element for k-model:');
663
- }
664
- }
665
-
666
- const htmlCreator = (tag) => document.createElement(tag);
667
- const svgCreator = (tag) => document.createElementNS('http://www.w3.org/2000/svg', tag);
668
- const mathMLCreator = (tag) => document.createElementNS('http://www.w3.org/1998/Math/MathML', tag);
669
- let creator = htmlCreator;
670
- /**
671
- * Create an enhanced HTMLElement.
672
- * - Only supports HTMLElements, **NOT** SVGElements or other Elements.
673
- * @param tag tag of an `HTMLElement`
674
- * @param attr attribute object or className
675
- * @param content a string or an array of HTMLEnhancedElement as child nodes
676
- *
677
- * ## About
678
- * @package @ktjs/core
679
- * @author Kasukabe Tsumugi <futami16237@gmail.com>
680
- * @version 0.28.2 (Last Update: 2026.02.10 14:46:21.243)
681
- * @license MIT
682
- * @link https://github.com/baendlorel/kt.js
683
- * @link https://baendlorel.github.io/ Welcome to my site!
684
- * @description Core functionality for kt.js - DOM manipulation utilities with JSX/TSX support
685
- * @copyright Copyright (c) 2026 Kasukabe Tsumugi. All rights reserved.
686
- */
687
- const h = (tag, attr, content) => {
688
- if (typeof tag !== 'string') {
689
- throw new Error('[kt.js error] tagName must be a string.');
690
- }
691
- if (attr) {
692
- if (SVG_ATTR_FLAG in attr) {
693
- delete attr[SVG_ATTR_FLAG];
694
- creator = svgCreator;
695
- }
696
- else if (MATHML_ATTR_FLAG in attr) {
697
- delete attr[MATHML_ATTR_FLAG];
698
- creator = mathMLCreator;
699
- }
700
- else {
701
- creator = htmlCreator;
702
- }
703
- }
704
- // * start creating the element
705
- const element = creator(tag);
706
- // * Handle content
707
- applyAttr(element, attr);
708
- applyContent(element, content);
709
- if (typeof attr === 'object' && attr !== null && 'k-model' in attr) {
710
- applyKModel(element, attr['k-model']);
711
- }
712
- return element;
713
- };
714
-
715
- /**
716
- * Fragment - Container component for managing arrays of child elements
717
- *
718
- * Features:
719
- * 1. Returns a comment anchor node, child elements are inserted after the anchor
720
- * 2. Supports reactive arrays, automatically updates DOM when array changes
721
- * 3. Basic version uses simple replacement algorithm (remove all old elements, insert all new elements)
722
- * 4. Future enhancement: key-based optimization
723
- *
724
- * Usage example:
725
- * ```tsx
726
- * const children = ref([<div>A</div>, <div>B</div>]);
727
- * const fragment = <Fragment children={children} />;
728
- * document.body.appendChild(fragment);
729
- *
730
- * // Automatic update
731
- * children.value = [<div>C</div>, <div>D</div>];
732
- * ```
733
- */
734
- function Fragment$1(props) {
735
- // key parameter reserved for future enhancement, currently unused
736
- // const { key: _key } = props;
737
- const redraw = () => {
738
- const newElements = childrenRef.value;
739
- const parent = anchor.parentNode;
740
- if (!parent) {
741
- // If anchor is not in DOM, only update internal state
742
- elements.length = 0;
743
- for (let i = 0; i < newElements.length; i++) {
744
- elements.push(newElements[i]);
745
- }
746
- return;
747
- }
748
- // Simple replacement algorithm: remove all old elements, insert all new elements
749
- // todo Future enhancement: key-based optimization
750
- // 1. Remove all old elements
751
- for (let i = 0; i < elements.length; i++) {
752
- elements[i].remove();
753
- }
754
- // 2. Insert all new elements
755
- const fragment = document.createDocumentFragment();
756
- elements.length = 0;
757
- for (let i = 0; i < newElements.length; i++) {
758
- const element = newElements[i];
759
- elements.push(element);
760
- fragment.appendChild(element);
761
- }
762
- // Insert after anchor
763
- parent.insertBefore(fragment, anchor.nextSibling);
764
- };
765
- let initialized = false;
766
- const childrenRef = toReactive(props.children, redraw);
767
- const elements = [];
768
- const anchor = document.createComment('kt-fragment');
769
- // Observe DOM insertion
770
- const observer = new MutationObserver(() => {
771
- if (anchor.isConnected && !initialized) {
772
- initialized = true;
773
- redraw();
774
- observer.disconnect();
775
- }
776
- });
777
- observer.observe(document.body, { childList: true, subtree: true });
778
- // Set ref reference
779
- $setRef(props, anchor);
780
- return anchor;
781
- }
782
- /**
783
- * Convert KTRawContent to HTMLElement array
784
- */
785
- function convertChildrenToElements(children) {
786
- const elements = [];
787
- const processChild = (child) => {
788
- if (child === undefined || child === null || child === false || child === true) {
789
- // Ignore null, undefined, false, true
790
- return;
791
- }
792
- if ($isArray(child)) {
793
- // Recursively process array
794
- $forEach(child, processChild);
795
- return;
796
- }
797
- if (typeof child === 'string' || typeof child === 'number') {
798
- // & Wrap text in span element? No! use text node instead
799
- const textNode = document.createTextNode(String(child));
800
- elements.push(textNode);
801
- return;
802
- }
803
- if (child instanceof HTMLElement) {
804
- elements.push(child);
805
- return;
806
- }
807
- if (isKT(child)) {
808
- processChild(child.value);
809
- return;
810
- }
811
- // Other types ignored or converted to string
812
- console.warn('[kt.js warn]','Fragment: unsupported child type', child);
813
- };
814
- processChild(children);
815
- return elements;
816
- }
817
-
818
- const create = (tag, props) => {
819
- if (typeof tag === 'function') {
820
- return tag(props);
821
- }
822
- else {
823
- return h(tag, props, props.children);
824
- }
825
- };
826
- const placeholder = () => document.createComment('k-if');
827
- /**
828
- * @param tag html tag or function component
829
- * @param props properties/attributes
830
- */
831
- function jsx(tag, props) {
832
- if (isComputed(props.ref)) {
833
- throw new Error('[kt.js error] Cannot assign a computed value to an element.');
834
- }
835
- let el;
836
- if ('k-if' in props) {
837
- const kif = props['k-if'];
838
- let condition = kif; // assume boolean by default
839
- // Handle reactive k-if
840
- if (isKT(kif)) {
841
- kif.addOnChange((newValue, oldValue) => {
842
- if (newValue === oldValue) {
843
- return;
844
- }
845
- const oldEl = el;
846
- $setRef(props, (el = newValue ? create(tag, props) : placeholder()));
847
- $replaceNode(oldEl, el);
848
- });
849
- condition = kif.value;
850
- }
851
- if (!condition) {
852
- // & make comment placeholder in case that ref might be redrawn later
853
- $setRef(props, (el = placeholder()));
854
- return el;
855
- }
856
- }
857
- $setRef(props, (el = create(tag, props)));
858
- return el;
859
- }
860
- /**
861
- * Fragment support - returns an array of children
862
- * Enhanced Fragment component that manages arrays of elements
863
- */
864
- function Fragment(props) {
865
- const { children } = props ?? {};
866
- if (!children) {
867
- return document.createComment('kt-fragment-empty');
868
- }
869
- const elements = convertChildrenToElements(children);
870
- return Fragment$1({ children: elements });
871
- }
872
- /**
873
- * JSX Development runtime - same as jsx but with additional dev checks
874
- */
875
- const jsxDEV = (...args) => {
876
- // console.log('JSX DEV called:', ...args);
877
- // console.log('children', (args[1] as any)?.children);
878
- return jsx(...args);
879
- };
880
- /**
881
- * JSX runtime for React 17+ automatic runtime
882
- * This is called when using jsx: "react-jsx" or "react-jsxdev"
883
- */
884
- const jsxs = jsx;
885
- /**
886
- * A helper to create redrawable elements
887
- * ```tsx
888
- * export function MyComponent() {
889
- * let aa = 10;
890
- * // ...
891
- * // aa might be changed
892
- * return createRedrawable(() => <div>{aa}</div>);
893
- * }
894
- * ```
895
- * Then the returned element has a `redraw` method to redraw itself with new values.
896
- * @param creator a simple creator function that returns an element
897
- * @returns created element's ref
898
- */
899
- function createRedrawable(creator) {
900
- const elRef = ref();
901
- const redraw = () => {
902
- elRef.value = creator(); // ref setter automatically calls replaceWith
903
- elRef.redraw = redraw;
904
- return elRef.value;
905
- };
906
- redraw();
907
- return elRef;
908
- }
909
-
910
- function KTAsync(props) {
911
- const raw = props.component(props);
912
- let comp = props.skeleton ?? document.createComment('ktjs-suspense-placeholder');
913
- if ($isThenable(raw)) {
914
- raw.then((resolved) => comp.replaceWith(resolved));
915
- }
916
- else {
917
- comp = raw;
918
- }
919
- return comp;
920
- }
921
-
922
- /**
923
- * KTFor - List rendering component with key-based optimization
924
- * Returns a Comment anchor node with rendered elements in __kt_for_list__
925
- */
926
- function KTFor(props) {
927
- const redraw = () => {
928
- const newList = listRef.value;
929
- const parent = anchor.parentNode;
930
- if (!parent) {
931
- // If not in DOM yet, just rebuild the list
932
- const newElements = [];
933
- nodeMap.clear();
934
- for (let index = 0; index < newList.length; index++) {
935
- const item = newList[index];
936
- const itemKey = currentKey(item, index, newList);
937
- const node = currentMap(item, index, newList);
938
- nodeMap.set(itemKey, node);
939
- newElements.push(node);
940
- }
941
- anchor.__kt_for_list__ = newElements;
942
- return anchor;
943
- }
944
- const oldLength = anchor.__kt_for_list__.length;
945
- const newLength = newList.length;
946
- // Fast path: empty list
947
- if (newLength === 0) {
948
- nodeMap.forEach((node) => node.remove());
949
- nodeMap.clear();
950
- anchor.__kt_for_list__ = [];
951
- return anchor;
952
- }
953
- // Fast path: all new items
954
- if (oldLength === 0) {
955
- const newElements = [];
956
- const fragment = document.createDocumentFragment();
957
- for (let i = 0; i < newLength; i++) {
958
- const item = newList[i];
959
- const itemKey = currentKey(item, i, newList);
960
- const node = currentMap(item, i, newList);
961
- nodeMap.set(itemKey, node);
962
- newElements.push(node);
963
- fragment.appendChild(node);
964
- }
965
- parent.insertBefore(fragment, anchor.nextSibling);
966
- anchor.__kt_for_list__ = newElements;
967
- return anchor;
968
- }
969
- // Build key index map and new elements array in one pass
970
- const newKeyToNewIndex = new Map();
971
- const newElements = new Array(newLength);
972
- let maxNewIndexSoFar = 0;
973
- let moved = false;
974
- for (let i = 0; i < newLength; i++) {
975
- const item = newList[i];
976
- const itemKey = currentKey(item, i, newList);
977
- newKeyToNewIndex.set(itemKey, i);
978
- if (nodeMap.has(itemKey)) {
979
- // Reuse existing node
980
- const node = nodeMap.get(itemKey);
981
- newElements[i] = node;
982
- // Track if items moved
983
- if (i < maxNewIndexSoFar) {
984
- moved = true;
985
- }
986
- else {
987
- maxNewIndexSoFar = i;
988
- }
989
- }
990
- else {
991
- // Create new node
992
- newElements[i] = currentMap(item, i, newList);
993
- }
994
- }
995
- // Remove nodes not in new list
996
- const toRemove = [];
997
- nodeMap.forEach((node, key) => {
998
- if (!newKeyToNewIndex.has(key)) {
999
- toRemove.push(node);
1000
- }
1001
- });
1002
- for (let i = 0; i < toRemove.length; i++) {
1003
- toRemove[i].remove();
1004
- }
1005
- // Update DOM with minimal operations
1006
- if (moved) {
1007
- // Use longest increasing subsequence to minimize moves
1008
- const seq = getSequence(newElements.map((el, i) => (nodeMap.has(currentKey(newList[i], i, newList)) ? i : -1)));
1009
- let j = seq.length - 1;
1010
- let anchor = null;
1011
- // Traverse from end to start for stable insertions
1012
- for (let i = newLength - 1; i >= 0; i--) {
1013
- const node = newElements[i];
1014
- if (j < 0 || i !== seq[j]) {
1015
- // Node needs to be moved or inserted
1016
- if (anchor) {
1017
- parent.insertBefore(node, anchor);
1018
- }
1019
- else {
1020
- // Insert at end
1021
- let nextSibling = anchor.nextSibling;
1022
- let temp = nextSibling;
1023
- while (temp && newElements.includes(temp)) {
1024
- temp = temp.nextSibling;
1025
- }
1026
- parent.insertBefore(node, temp);
1027
- }
1028
- }
1029
- else {
1030
- j--;
1031
- }
1032
- anchor = node;
1033
- }
1034
- }
1035
- else {
1036
- // No moves needed, just insert new nodes
1037
- let currentNode = anchor.nextSibling;
1038
- for (let i = 0; i < newLength; i++) {
1039
- const node = newElements[i];
1040
- if (currentNode !== node) {
1041
- parent.insertBefore(node, currentNode);
1042
- }
1043
- else {
1044
- currentNode = currentNode.nextSibling;
1045
- }
1046
- }
1047
- }
1048
- // Update maps
1049
- nodeMap.clear();
1050
- for (let i = 0; i < newLength; i++) {
1051
- const itemKey = currentKey(newList[i], i, newList);
1052
- nodeMap.set(itemKey, newElements[i]);
1053
- }
1054
- anchor.__kt_for_list__ = newElements;
1055
- return anchor;
1056
- };
1057
- const { key: currentKey = (item) => item, map: currentMap } = props;
1058
- const listRef = toReactive(props.list, redraw);
1059
- const anchor = document.createComment('kt-for');
1060
- // Map to track rendered nodes by key
1061
- const nodeMap = new Map();
1062
- // Render initial list
1063
- const elements = [];
1064
- for (let index = 0; index < listRef.value.length; index++) {
1065
- const item = listRef.value[index];
1066
- const itemKey = currentKey(item, index, listRef.value);
1067
- const node = currentMap(item, index, listRef.value);
1068
- nodeMap.set(itemKey, node);
1069
- elements.push(node);
1070
- }
1071
- anchor.__kt_for_list__ = elements;
1072
- $setRef(props, anchor);
1073
- return anchor;
1074
- }
1075
- // Longest Increasing Subsequence algorithm (optimized for diff)
1076
- function getSequence(arr) {
1077
- const p = arr.slice();
1078
- const result = [0];
1079
- let i, j, u, v, c;
1080
- const len = arr.length;
1081
- for (i = 0; i < len; i++) {
1082
- const arrI = arr[i];
1083
- if (arrI === -1)
1084
- continue;
1085
- j = result[result.length - 1];
1086
- if (arr[j] < arrI) {
1087
- p[i] = j;
1088
- result.push(i);
1089
- continue;
1090
- }
1091
- u = 0;
1092
- v = result.length - 1;
1093
- while (u < v) {
1094
- c = ((u + v) / 2) | 0;
1095
- if (arr[result[c]] < arrI) {
1096
- u = c + 1;
1097
- }
1098
- else {
1099
- v = c;
1100
- }
1101
- }
1102
- if (arrI < arr[result[u]]) {
1103
- if (u > 0) {
1104
- p[i] = result[u - 1];
1105
- }
1106
- result[u] = i;
1107
- }
1108
- }
1109
- u = result.length;
1110
- v = result[u - 1];
1111
- while (u-- > 0) {
1112
- result[u] = v;
1113
- v = p[v];
1114
- }
1115
- return result;
1116
- }
1117
-
1118
- exports.$modelOrRef = $modelOrRef;
1119
- exports.$setRef = $setRef;
1120
- exports.Fragment = Fragment;
1121
- exports.KTAsync = KTAsync;
1122
- exports.KTComputed = KTComputed;
1123
- exports.KTFor = KTFor;
1124
- exports.KTRef = KTRef;
1125
- exports.computed = computed;
1126
- exports.createElement = h;
1127
- exports.createRedrawable = createRedrawable;
1128
- exports.dereactive = dereactive;
1129
- exports.effect = effect;
1130
- exports.h = h;
1131
- exports.isComputed = isComputed;
1132
- exports.isKT = isKT;
1133
- exports.isRef = isRef;
1134
- exports.jsx = jsx;
1135
- exports.jsxDEV = jsxDEV;
1136
- exports.jsxs = jsxs;
1137
- exports.ref = ref;
1138
- exports.surfaceRef = surfaceRef;
1139
- exports.toReactive = toReactive;
1140
- exports.toRef = toRef;
1141
-
1142
- return exports;
1143
-
1144
- })({});