@ktjs/core 0.29.9 → 0.29.11

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