@ktjs/core 0.23.0 → 0.24.0

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,11 +1,35 @@
1
+ // Shared constants
2
+ // Empty for now - can be extended with framework-wide constants
3
+ /**
4
+ * Mark the attribute as SVG to handle special cases during rendering.
5
+ */
6
+ const SVG_ATTR_FLAG = '__kt_svg__';
7
+ /**
8
+ * Mark the attribute as MathML to handle special cases during rendering.
9
+ */
10
+ const MATHML_ATTR_FLAG = '__kt_mathml__';
11
+
1
12
  // Cached native methods for performance optimization
2
13
  const $isArray = Array.isArray;
14
+ const $is = Object.is;
3
15
  const $entries = Object.entries;
4
- const $isThenable = (o) => typeof o === 'object' && o !== null && typeof o.then === 'function';
16
+ const $random = Math.random;
17
+ const $isThenable = (o) => typeof o?.then === 'function';
5
18
 
6
19
  // DOM manipulation utilities
7
20
  // # dom natives
8
21
  const $isNode = (x) => x?.nodeType > 0;
22
+ /**
23
+ * Safe replace `oldNode` With `newNode`
24
+ */
25
+ const $replaceNode = (oldNode, newNode) => {
26
+ if ($isNode(oldNode) && $isNode(newNode)) {
27
+ if (newNode.contains(oldNode)) {
28
+ newNode.remove();
29
+ }
30
+ oldNode.replaceWith(newNode);
31
+ }
32
+ };
9
33
  /**
10
34
  * & Remove `bind` because it is shockingly slower than wrapper
11
35
  * & `window.document` is safe because it is not configurable and its setter is undefined
@@ -51,130 +75,21 @@ const applyModel = (element, valueRef, propName, eventName) => {
51
75
  element.addEventListener(eventName, () => (valueRef.value = element[propName]));
52
76
  };
53
77
 
78
+ if (typeof Symbol === 'undefined') {
79
+ window.Symbol = function Symbol(description) {
80
+ return `@@SYMBOL_${description || ''}_${$random().toString(36).slice(2)}`;
81
+ };
82
+ }
83
+
54
84
  // Shared utilities and cached native methods for kt.js framework
55
- // import './misc/symbol-polyfill.js';
56
- // Re-export all utilities
57
- Object.defineProperty(window, '__ktjs__', { value: '0.22.3' });
85
+ Object.defineProperty(window, '__ktjs__', { value: '0.22.6' });
58
86
 
59
- class KTRef {
60
- /**
61
- * Indicates that this is a KTRef instance
62
- */
63
- isKT = true;
64
- /**
65
- * @internal
66
- */
67
- _value;
68
- /**
69
- * @internal
70
- */
71
- _onChanges;
72
- constructor(_value, _onChanges) {
73
- this._value = _value;
74
- this._onChanges = _onChanges;
75
- }
76
- /**
77
- * If new value and old value are both nodes, the old one will be replaced in the DOM
78
- */
79
- get value() {
80
- return this._value;
81
- }
82
- set value(newValue) {
83
- if (newValue === this._value) {
84
- return;
85
- }
86
- // replace the old node with the new one in the DOM if both are nodes
87
- if (this._value instanceof Node && newValue instanceof Node) {
88
- if (newValue.contains(this._value)) {
89
- this._value.remove();
90
- }
91
- this._value.replaceWith(newValue);
92
- }
93
- const oldValue = this._value;
94
- this._value = newValue;
95
- for (let i = 0; i < this._onChanges.length; i++) {
96
- this._onChanges[i](newValue, oldValue);
97
- }
98
- }
99
- /**
100
- * Register a callback when the value changes
101
- * @param callback (newValue, oldValue) => xxx
102
- */
103
- addOnChange(callback) {
104
- if (typeof callback !== 'function') {
105
- throw new Error('[kt.js error] KTRef.addOnChange: callback must be a function');
106
- }
107
- this._onChanges.push(callback);
108
- }
109
- removeOnChange(callback) {
110
- for (let i = this._onChanges.length - 1; i >= 0; i--) {
111
- if (this._onChanges[i] === callback) {
112
- this._onChanges.splice(i, 1);
113
- return true;
114
- }
115
- }
116
- return false;
117
- }
118
- }
119
- const isKTRef = (obj) => obj?.isKT === true;
120
- /**
121
- * Reference to the created HTML element.
122
- * - **Only** respond to `ref.value` changes, not reactive to internal changes of the element.
123
- * - can alse be used to store normal values, but it is not reactive.
124
- * - if the value is already a `KTRef`, it will be returned **directly**.
125
- * @param value mostly an HTMLElement
126
- */
127
- function ref(value, onChange) {
128
- if (isKTRef(value)) {
129
- if (onChange) {
130
- value.addOnChange(onChange);
131
- }
132
- return value;
133
- }
134
- return new KTRef(value, onChange ? [onChange] : []);
135
- }
136
- function deref(value) {
137
- return isKTRef(value) ? value.value : value;
138
- }
139
- function kcollect() {
140
- const newObj = {};
141
- const entries = $entries(this);
142
- for (let i = 0; i < entries.length; i++) {
143
- const key = entries[i][0];
144
- if (key === 'kcollect') {
145
- continue;
146
- }
147
- newObj[key] = entries[i][1].value;
148
- }
149
- return newObj;
150
- }
151
- /**
152
- * Make all first-level properties of the object a `KTRef`.
153
- * - `obj.a.b` is not reactive
154
- */
155
- const surfaceRef = (obj) => {
156
- const entries = $entries(obj);
157
- const newObj = { kcollect };
158
- for (let i = 0; i < entries.length; i++) {
159
- newObj[entries[i][0]] = ref(entries[i][1]);
160
- }
161
- return newObj;
162
- };
163
- // # asserts
164
- /**
165
- * Assert k-model to be a ref object
166
- */
167
- const $modelOrRef = (props, defaultValue) => {
168
- // & props is an object. Won't use it in any other place
169
- if ('k-model' in props) {
170
- const kmodel = props['k-model'];
171
- if (!kmodel?.isKT) {
172
- throw new Error(`[kt.js error] k-model data must be a KTRef object, please use 'ref(...)' to wrap it.`);
173
- }
174
- return kmodel;
175
- }
176
- return ref(defaultValue);
177
- };
87
+ // export const KT_TYPE_REF = 1 as const;
88
+ // export const KT_TYPE_COMPUTED = 2 as const;
89
+ // export type KTReactiveTypeEnum = typeof KT_TYPE_REF | typeof KT_TYPE_COMPUTED;
90
+ const isKT = (obj) => obj?.isKT;
91
+ const isRef = (obj) => obj?.ktType === 1 /* KTReactiveType.REF */;
92
+ const isComputed = (obj) => obj?.ktType === 2 /* KTReactiveType.COMPUTED */;
178
93
 
179
94
  const booleanHandler = (element, key, value) => {
180
95
  if (key in element) {
@@ -236,7 +151,7 @@ function attrIsObject(element, attr) {
236
151
  }
237
152
  if ('k-html' in attr) {
238
153
  const html = attr['k-html'];
239
- if (isKTRef(html)) {
154
+ if (isKT(html)) {
240
155
  element.innerHTML = html.value;
241
156
  html.addOnChange((v) => (element.innerHTML = v));
242
157
  }
@@ -245,14 +160,14 @@ function attrIsObject(element, attr) {
245
160
  }
246
161
  }
247
162
  for (const key in attr) {
248
- if (key === 'class' ||
163
+ if (key === 'k-if' ||
164
+ key === 'k-model' ||
165
+ key === 'ref' ||
166
+ key === 'class' ||
249
167
  key === 'className' ||
250
168
  key === 'style' ||
251
169
  key === 'children' ||
252
- key === 'k-if' ||
253
- key === 'k-model' ||
254
- key === 'k-html' ||
255
- key === 'ref') {
170
+ key === 'k-html') {
256
171
  continue;
257
172
  }
258
173
  const o = attr[key];
@@ -285,7 +200,7 @@ function apdSingle(element, c) {
285
200
  if (c === false || c === undefined || c === null) {
286
201
  return;
287
202
  }
288
- if (isKTRef(c)) {
203
+ if (isKT(c)) {
289
204
  let node = assureNode(c.value);
290
205
  $append.call(element, node);
291
206
  c.addOnChange((newValue, oldValue) => {
@@ -342,8 +257,218 @@ function applyContent(element, content) {
342
257
  }
343
258
  }
344
259
 
260
+ class KTRef {
261
+ /**
262
+ * Indicates that this is a KTRef instance
263
+ */
264
+ isKT = true;
265
+ ktType = 1 /* KTReactiveType.REF */;
266
+ /**
267
+ * @internal
268
+ */
269
+ _value;
270
+ /**
271
+ * @internal
272
+ */
273
+ _onChanges;
274
+ constructor(_value, _onChanges) {
275
+ this._value = _value;
276
+ this._onChanges = _onChanges;
277
+ }
278
+ /**
279
+ * If new value and old value are both nodes, the old one will be replaced in the DOM
280
+ */
281
+ get value() {
282
+ return this._value;
283
+ }
284
+ set value(newValue) {
285
+ if ($is(newValue, this._value)) {
286
+ return;
287
+ }
288
+ const oldValue = this._value;
289
+ $replaceNode(oldValue, newValue);
290
+ this._value = newValue;
291
+ for (let i = 0; i < this._onChanges.length; i++) {
292
+ this._onChanges[i](newValue, oldValue);
293
+ }
294
+ }
295
+ /**
296
+ * Register a callback when the value changes
297
+ * @param callback (newValue, oldValue) => xxx
298
+ */
299
+ addOnChange(callback) {
300
+ if (typeof callback !== 'function') {
301
+ throw new Error('[kt.js error] KTRef.addOnChange: callback must be a function');
302
+ }
303
+ this._onChanges.push(callback);
304
+ }
305
+ removeOnChange(callback) {
306
+ for (let i = this._onChanges.length - 1; i >= 0; i--) {
307
+ if (this._onChanges[i] === callback) {
308
+ this._onChanges.splice(i, 1);
309
+ return true;
310
+ }
311
+ }
312
+ return false;
313
+ }
314
+ }
315
+ /**
316
+ * Reference to the created HTML element.
317
+ * - **Only** respond to `ref.value` changes, not reactive to internal changes of the element.
318
+ * - can alse be used to store normal values, but it is not reactive.
319
+ * - if the value is already a `KTRef`, it will be returned **directly**.
320
+ * @param value mostly an HTMLElement
321
+ */
322
+ function ref(value, onChange) {
323
+ return new KTRef(value, onChange ? [onChange] : []);
324
+ }
325
+ /**
326
+ * Convert a value to `KTRef`.
327
+ * - Returns the original value if it is already a `KTRef`.
328
+ * - Throws error if the value is a `KTComputed`.
329
+ * - Otherwise wraps the value with `ref()`.
330
+ * @param o value to convert
331
+ */
332
+ const toRef = (o) => {
333
+ if (isRef(o)) {
334
+ return o;
335
+ }
336
+ else if (isComputed(o)) {
337
+ throw new Error('[kt.js error] Computed values cannot be used as KTRef.');
338
+ }
339
+ else {
340
+ return ref(o);
341
+ }
342
+ };
343
+ function deref(value) {
344
+ return isKT(value) ? value.value : value;
345
+ }
346
+ function kcollect() {
347
+ const newObj = {};
348
+ const entries = $entries(this);
349
+ for (let i = 0; i < entries.length; i++) {
350
+ const key = entries[i][0];
351
+ if (key === 'kcollect') {
352
+ continue;
353
+ }
354
+ newObj[key] = entries[i][1].value;
355
+ }
356
+ return newObj;
357
+ }
358
+ /**
359
+ * Make all first-level properties of the object a `KTRef`.
360
+ * - `obj.a.b` is not reactive
361
+ */
362
+ const surfaceRef = (obj) => {
363
+ const entries = $entries(obj);
364
+ const newObj = { kcollect };
365
+ for (let i = 0; i < entries.length; i++) {
366
+ newObj[entries[i][0]] = ref(entries[i][1]);
367
+ }
368
+ return newObj;
369
+ };
370
+ // # asserts
371
+ /**
372
+ * Assert k-model to be a ref object
373
+ */
374
+ const $modelOrRef = (props, defaultValue) => {
375
+ // & props is an object. Won't use it in any other place
376
+ if ('k-model' in props) {
377
+ const kmodel = props['k-model'];
378
+ if (!kmodel?.isKT) {
379
+ throw new Error(`[kt.js error] k-model data must be a KTRef object, please use 'ref(...)' to wrap it.`);
380
+ }
381
+ return kmodel;
382
+ }
383
+ return ref(defaultValue);
384
+ };
385
+
386
+ class KTComputed {
387
+ /**
388
+ * Indicates that this is a KTRef instance
389
+ */
390
+ isKT = true;
391
+ ktType = 2 /* KTReactiveType.COMPUTED */;
392
+ /**
393
+ * @internal
394
+ */
395
+ _calculator;
396
+ /**
397
+ * @internal
398
+ */
399
+ _value;
400
+ /**
401
+ * @internal
402
+ */
403
+ _onChanges = [];
404
+ _subscribe(reactives) {
405
+ for (let i = 0; i < reactives.length; i++) {
406
+ const reactive = reactives[i];
407
+ reactive.addOnChange(() => {
408
+ const oldValue = this._value;
409
+ this._value = this._calculator();
410
+ if (oldValue === this._value) {
411
+ return;
412
+ }
413
+ $replaceNode(oldValue, this._value);
414
+ for (let i = 0; i < this._onChanges.length; i++) {
415
+ this._onChanges[i](this._value, oldValue);
416
+ }
417
+ });
418
+ }
419
+ }
420
+ constructor(_calculator, reactives) {
421
+ this._calculator = _calculator;
422
+ this._value = _calculator();
423
+ this._subscribe(reactives);
424
+ }
425
+ /**
426
+ * If new value and old value are both nodes, the old one will be replaced in the DOM
427
+ */
428
+ get value() {
429
+ return this._value;
430
+ }
431
+ set value(_newValue) {
432
+ throw new Error('[kt.js error] KTComputed: cannot set value of a computed value');
433
+ }
434
+ /**
435
+ * Register a callback when the value changes
436
+ * @param callback (newValue, oldValue) => xxx
437
+ */
438
+ addOnChange(callback) {
439
+ if (typeof callback !== 'function') {
440
+ throw new Error('[kt.js error] KTRef.addOnChange: callback must be a function');
441
+ }
442
+ this._onChanges.push(callback);
443
+ }
444
+ /**
445
+ * Unregister a callback
446
+ * @param callback (newValue, oldValue) => xxx
447
+ */
448
+ removeOnChange(callback) {
449
+ for (let i = this._onChanges.length - 1; i >= 0; i--) {
450
+ if (this._onChanges[i] === callback) {
451
+ this._onChanges.splice(i, 1);
452
+ return true;
453
+ }
454
+ }
455
+ return false;
456
+ }
457
+ }
458
+ /**
459
+ * Create a reactive computed value
460
+ * @param computeFn
461
+ * @param reactives refs and computeds that this computed depends on
462
+ */
463
+ function computed(computeFn, reactives) {
464
+ if (reactives.some((v) => !isKT(v))) {
465
+ throw new Error('[kt.js error] computed: all reactives must be KTRef or KTComputed instances');
466
+ }
467
+ return new KTComputed(computeFn, reactives);
468
+ }
469
+
345
470
  function applyKModel(element, valueRef) {
346
- if (!isKTRef(valueRef)) {
471
+ if (!isKT(valueRef)) {
347
472
  console.warn('[kt.js warn] k-model value must be a KTRef.');
348
473
  return;
349
474
  }
@@ -370,9 +495,6 @@ const htmlCreator = (tag) => document.createElement(tag);
370
495
  const svgCreator = (tag) => document.createElementNS('http://www.w3.org/2000/svg', tag);
371
496
  const mathMLCreator = (tag) => document.createElementNS('http://www.w3.org/1998/Math/MathML', tag);
372
497
  let creator = htmlCreator;
373
- // # consts
374
- const SVG_ATTR_FLAG = '__kt_svg__';
375
- const MATHML_ATTR_FLAG = '__kt_mathml__';
376
498
  /**
377
499
  * Create an enhanced HTMLElement.
378
500
  * - Only supports HTMLElements, **NOT** SVGElements or other Elements.
@@ -383,7 +505,7 @@ const MATHML_ATTR_FLAG = '__kt_mathml__';
383
505
  * ## About
384
506
  * @package @ktjs/core
385
507
  * @author Kasukabe Tsumugi <futami16237@gmail.com>
386
- * @version 0.23.0 (Last Update: 2026.02.03 17:15:01.812)
508
+ * @version 0.24.0 (Last Update: 2026.02.05 12:08:54.164)
387
509
  * @license MIT
388
510
  * @link https://github.com/baendlorel/kt.js
389
511
  * @link https://baendlorel.github.io/ Welcome to my site!
@@ -413,13 +535,7 @@ const h = (tag, attr, content) => {
413
535
  applyAttr(element, attr);
414
536
  applyContent(element, content);
415
537
  if (typeof attr === 'object' && attr !== null && 'k-model' in attr) {
416
- const kmodel = attr['k-model'];
417
- if (isKTRef(kmodel)) {
418
- applyKModel(element, kmodel);
419
- }
420
- else {
421
- throw new Error('[kt.js error] k-model value must be a KTRef.');
422
- }
538
+ applyKModel(element, attr['k-model']);
423
539
  }
424
540
  return element;
425
541
  };
@@ -439,20 +555,23 @@ const placeholder = () => document.createComment('k-if');
439
555
  * @param props properties/attributes
440
556
  */
441
557
  function jsx(tag, props) {
442
- const maybeDummyRef = isKTRef(props.ref) ? props.ref : dummyRef;
558
+ if (isComputed(props.ref)) {
559
+ throw new Error('[kt.js error] Cannot assign a computed value to an element.');
560
+ }
561
+ const maybeDummyRef = isRef(props.ref) ? props.ref : dummyRef;
443
562
  let el;
444
563
  if ('k-if' in props) {
445
564
  const kif = props['k-if'];
446
565
  let condition = kif; // assume boolean by default
447
566
  // Handle reactive k-if
448
- if (isKTRef(kif)) {
567
+ if (isKT(kif)) {
449
568
  kif.addOnChange((newValue, oldValue) => {
450
569
  if (newValue === oldValue) {
451
570
  return;
452
571
  }
453
572
  const oldEl = el;
454
573
  el = newValue ? create(tag, props) : placeholder();
455
- oldEl.replaceWith(el);
574
+ $replaceNode(oldEl, el);
456
575
  maybeDummyRef.value = el;
457
576
  });
458
577
  condition = kif.value;
@@ -709,7 +828,7 @@ function KTFor(props) {
709
828
  return anchor;
710
829
  };
711
830
  // Set ref if provided
712
- if (props.ref?.isKT) {
831
+ if (isRef(props.ref)) {
713
832
  props.ref.value = anchor;
714
833
  }
715
834
  return anchor;
@@ -757,4 +876,4 @@ function getSequence(arr) {
757
876
  return result;
758
877
  }
759
878
 
760
- export { $modelOrRef, Fragment, KTAsync, KTFor, KTRef, h as createElement, createRedrawable, deref, h, isKTRef, jsx, jsxDEV, jsxs, ref, surfaceRef };
879
+ export { $modelOrRef, Fragment, KTAsync, KTComputed, KTFor, KTRef, computed, h as createElement, createRedrawable, deref, h, isComputed, isKT, isRef, jsx, jsxDEV, jsxs, ref, surfaceRef, toRef };