@ktjs/core 0.22.5 → 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.
@@ -1,10 +1,34 @@
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;
3
- const $entries = Object.entries;
4
- const $isThenable = (o) => typeof o === 'object' && o !== null && typeof o.then === 'function';
14
+ const $is = Object.is;
15
+ const $random = Math.random;
16
+ const $isThenable = (o) => typeof o?.then === 'function';
5
17
 
6
18
  // DOM manipulation utilities
7
19
  // # dom natives
20
+ const $isNode = (x) => x?.nodeType > 0;
21
+ /**
22
+ * Safe replace `oldNode` With `newNode`
23
+ */
24
+ const $replaceNode = (oldNode, newNode) => {
25
+ if ($isNode(oldNode) && $isNode(newNode)) {
26
+ if (newNode.contains(oldNode)) {
27
+ newNode.remove();
28
+ }
29
+ oldNode.replaceWith(newNode);
30
+ }
31
+ };
8
32
  /**
9
33
  * & Remove `bind` because it is shockingly slower than wrapper
10
34
  * & `window.document` is safe because it is not configurable and its setter is undefined
@@ -50,129 +74,21 @@ const applyModel = (element, valueRef, propName, eventName) => {
50
74
  element.addEventListener(eventName, () => (valueRef.value = element[propName]));
51
75
  };
52
76
 
77
+ if (typeof Symbol === 'undefined') {
78
+ window.Symbol = function Symbol(description) {
79
+ return `@@SYMBOL_${description || ''}_${$random().toString(36).slice(2)}`;
80
+ };
81
+ }
82
+
53
83
  // Shared utilities and cached native methods for kt.js framework
54
- // Re-export all utilities
55
- Object.defineProperty(window, '__ktjs__', { value: '0.22.2' });
84
+ Object.defineProperty(window, '__ktjs__', { value: '0.22.6' });
56
85
 
57
- class KTRef {
58
- /**
59
- * Indicates that this is a KTRef instance
60
- */
61
- isKT = true;
62
- /**
63
- * @internal
64
- */
65
- _value;
66
- /**
67
- * @internal
68
- */
69
- _onChanges;
70
- constructor(_value, _onChanges) {
71
- this._value = _value;
72
- this._onChanges = _onChanges;
73
- }
74
- /**
75
- * If new value and old value are both nodes, the old one will be replaced in the DOM
76
- */
77
- get value() {
78
- return this._value;
79
- }
80
- set value(newValue) {
81
- if (newValue === this._value) {
82
- return;
83
- }
84
- // replace the old node with the new one in the DOM if both are nodes
85
- if (this._value instanceof Node && newValue instanceof Node) {
86
- if (newValue.contains(this._value)) {
87
- this._value.remove();
88
- }
89
- this._value.replaceWith(newValue);
90
- }
91
- const oldValue = this._value;
92
- this._value = newValue;
93
- for (let i = 0; i < this._onChanges.length; i++) {
94
- this._onChanges[i](newValue, oldValue);
95
- }
96
- }
97
- /**
98
- * Register a callback when the value changes
99
- * @param callback (newValue, oldValue) => xxx
100
- */
101
- addOnChange(callback) {
102
- if (typeof callback !== 'function') {
103
- throw new Error('[kt.js error] KTRef.addOnChange: callback must be a function');
104
- }
105
- this._onChanges.push(callback);
106
- }
107
- removeOnChange(callback) {
108
- for (let i = this._onChanges.length - 1; i >= 0; i--) {
109
- if (this._onChanges[i] === callback) {
110
- this._onChanges.splice(i, 1);
111
- return true;
112
- }
113
- }
114
- return false;
115
- }
116
- }
117
- const isKTRef = (obj) => obj?.isKT === true;
118
- /**
119
- * Reference to the created HTML element.
120
- * - **Only** respond to `ref.value` changes, not reactive to internal changes of the element.
121
- * - can alse be used to store normal values, but it is not reactive.
122
- * - if the value is already a `KTRef`, it will be returned **directly**.
123
- * @param value mostly an HTMLElement
124
- */
125
- function ref(value, onChange) {
126
- if (isKTRef(value)) {
127
- if (onChange) {
128
- value.addOnChange(onChange);
129
- }
130
- return value;
131
- }
132
- return new KTRef(value, onChange ? [onChange] : []);
133
- }
134
- function deref(value) {
135
- return isKTRef(value) ? value.value : value;
136
- }
137
- function kcollect() {
138
- const newObj = {};
139
- const entries = $entries(this);
140
- for (let i = 0; i < entries.length; i++) {
141
- const key = entries[i][0];
142
- if (key === 'kcollect') {
143
- continue;
144
- }
145
- newObj[key] = entries[i][1].value;
146
- }
147
- return newObj;
148
- }
149
- /**
150
- * Make all first-level properties of the object a `KTRef`.
151
- * - `obj.a.b` is not reactive
152
- */
153
- const surfaceRef = (obj) => {
154
- const entries = $entries(obj);
155
- const newObj = { kcollect };
156
- for (let i = 0; i < entries.length; i++) {
157
- newObj[entries[i][0]] = ref(entries[i][1]);
158
- }
159
- return newObj;
160
- };
161
- // # asserts
162
- /**
163
- * Assert k-model to be a ref object
164
- */
165
- const $modelOrRef = (props, defaultValue) => {
166
- // & props is an object. Won't use it in any other place
167
- if ('k-model' in props) {
168
- const kmodel = props['k-model'];
169
- if (!kmodel?.isKT) {
170
- throw new Error(`[kt.js error] k-model data must be a KTRef object, please use 'ref(...)' to wrap it.`);
171
- }
172
- return kmodel;
173
- }
174
- return ref(defaultValue);
175
- };
86
+ // export const KT_TYPE_REF = 1 as const;
87
+ // export const KT_TYPE_COMPUTED = 2 as const;
88
+ // export type KTReactiveTypeEnum = typeof KT_TYPE_REF | typeof KT_TYPE_COMPUTED;
89
+ const isKT = (obj) => obj?.isKT;
90
+ const isRef = (obj) => obj?.ktType === 1 /* KTReactiveType.REF */;
91
+ const isComputed = (obj) => obj?.ktType === 2 /* KTReactiveType.COMPUTED */;
176
92
 
177
93
  const booleanHandler = (element, key, value) => {
178
94
  if (key in element) {
@@ -232,14 +148,25 @@ function attrIsObject(element, attr) {
232
148
  }
233
149
  }
234
150
  }
151
+ if ('k-html' in attr) {
152
+ const html = attr['k-html'];
153
+ if (isKT(html)) {
154
+ element.innerHTML = html.value;
155
+ html.addOnChange((v) => (element.innerHTML = v));
156
+ }
157
+ else {
158
+ element.innerHTML = html;
159
+ }
160
+ }
235
161
  for (const key in attr) {
236
- if (key === 'class' ||
162
+ if (key === 'k-if' ||
163
+ key === 'k-model' ||
164
+ key === 'ref' ||
165
+ key === 'class' ||
237
166
  key === 'className' ||
238
167
  key === 'style' ||
239
168
  key === 'children' ||
240
- key === 'k-if' ||
241
- key.startsWith('k-model') ||
242
- key === 'ref') {
169
+ key === 'k-html') {
243
170
  continue;
244
171
  }
245
172
  const o = attr[key];
@@ -249,6 +176,7 @@ function attrIsObject(element, attr) {
249
176
  }
250
177
  // normal attributes
251
178
  else {
179
+ // todo 这里也可以绑定ref的
252
180
  (handlers[key] || defaultHandler)(element, key, o);
253
181
  }
254
182
  }
@@ -265,13 +193,25 @@ function applyAttr(element, attr) {
265
193
  }
266
194
  }
267
195
 
196
+ const assureNode = (o) => ($isNode(o) ? o : document.createTextNode(o));
268
197
  function apdSingle(element, c) {
269
198
  // & JSX should ignore false, undefined, and null
270
199
  if (c === false || c === undefined || c === null) {
271
200
  return;
272
201
  }
273
- if (typeof c === 'object' && c !== null && 'isKT' in c) {
274
- $append.call(element, c.value);
202
+ if (isKT(c)) {
203
+ let node = assureNode(c.value);
204
+ $append.call(element, node);
205
+ c.addOnChange((newValue, oldValue) => {
206
+ if ($isNode(newValue) && $isNode(oldValue)) {
207
+ // & this case is handled automically in `class KTRef`
208
+ // todo 2 cases might be able to merge into 1
209
+ return;
210
+ }
211
+ const oldNode = node;
212
+ node = assureNode(newValue);
213
+ oldNode.replaceWith(node);
214
+ });
275
215
  }
276
216
  else {
277
217
  $append.call(element, c);
@@ -316,8 +256,74 @@ function applyContent(element, content) {
316
256
  }
317
257
  }
318
258
 
259
+ class KTRef {
260
+ /**
261
+ * Indicates that this is a KTRef instance
262
+ */
263
+ isKT = true;
264
+ ktType = 1 /* KTReactiveType.REF */;
265
+ /**
266
+ * @internal
267
+ */
268
+ _value;
269
+ /**
270
+ * @internal
271
+ */
272
+ _onChanges;
273
+ constructor(_value, _onChanges) {
274
+ this._value = _value;
275
+ this._onChanges = _onChanges;
276
+ }
277
+ /**
278
+ * If new value and old value are both nodes, the old one will be replaced in the DOM
279
+ */
280
+ get value() {
281
+ return this._value;
282
+ }
283
+ set value(newValue) {
284
+ if ($is(newValue, this._value)) {
285
+ return;
286
+ }
287
+ const oldValue = this._value;
288
+ $replaceNode(oldValue, newValue);
289
+ this._value = newValue;
290
+ for (let i = 0; i < this._onChanges.length; i++) {
291
+ this._onChanges[i](newValue, oldValue);
292
+ }
293
+ }
294
+ /**
295
+ * Register a callback when the value changes
296
+ * @param callback (newValue, oldValue) => xxx
297
+ */
298
+ addOnChange(callback) {
299
+ if (typeof callback !== 'function') {
300
+ throw new Error('[kt.js error] KTRef.addOnChange: callback must be a function');
301
+ }
302
+ this._onChanges.push(callback);
303
+ }
304
+ removeOnChange(callback) {
305
+ for (let i = this._onChanges.length - 1; i >= 0; i--) {
306
+ if (this._onChanges[i] === callback) {
307
+ this._onChanges.splice(i, 1);
308
+ return true;
309
+ }
310
+ }
311
+ return false;
312
+ }
313
+ }
314
+ /**
315
+ * Reference to the created HTML element.
316
+ * - **Only** respond to `ref.value` changes, not reactive to internal changes of the element.
317
+ * - can alse be used to store normal values, but it is not reactive.
318
+ * - if the value is already a `KTRef`, it will be returned **directly**.
319
+ * @param value mostly an HTMLElement
320
+ */
321
+ function ref(value, onChange) {
322
+ return new KTRef(value, []);
323
+ }
324
+
319
325
  function applyKModel(element, valueRef) {
320
- if (!isKTRef(valueRef)) {
326
+ if (!isKT(valueRef)) {
321
327
  console.warn('[kt.js warn] k-model value must be a KTRef.');
322
328
  return;
323
329
  }
@@ -344,9 +350,6 @@ const htmlCreator = (tag) => document.createElement(tag);
344
350
  const svgCreator = (tag) => document.createElementNS('http://www.w3.org/2000/svg', tag);
345
351
  const mathMLCreator = (tag) => document.createElementNS('http://www.w3.org/1998/Math/MathML', tag);
346
352
  let creator = htmlCreator;
347
- // # consts
348
- const SVG_ATTR_FLAG = '__kt_svg__';
349
- const MATHML_ATTR_FLAG = '__kt_mathml__';
350
353
  /**
351
354
  * Create an enhanced HTMLElement.
352
355
  * - Only supports HTMLElements, **NOT** SVGElements or other Elements.
@@ -357,7 +360,7 @@ const MATHML_ATTR_FLAG = '__kt_mathml__';
357
360
  * ## About
358
361
  * @package @ktjs/core
359
362
  * @author Kasukabe Tsumugi <futami16237@gmail.com>
360
- * @version 0.22.5 (Last Update: 2026.02.03 09:07:21.411)
363
+ * @version 0.24.0 (Last Update: 2026.02.05 12:08:54.164)
361
364
  * @license MIT
362
365
  * @link https://github.com/baendlorel/kt.js
363
366
  * @link https://baendlorel.github.io/ Welcome to my site!
@@ -387,13 +390,7 @@ const h = (tag, attr, content) => {
387
390
  applyAttr(element, attr);
388
391
  applyContent(element, content);
389
392
  if (typeof attr === 'object' && attr !== null && 'k-model' in attr) {
390
- const kmodel = attr['k-model'];
391
- if (isKTRef(kmodel)) {
392
- applyKModel(element, kmodel);
393
- }
394
- else {
395
- throw new Error('[kt.js error] k-model value must be a KTRef.');
396
- }
393
+ applyKModel(element, attr['k-model']);
397
394
  }
398
395
  return element;
399
396
  };
@@ -413,20 +410,23 @@ const placeholder = () => document.createComment('k-if');
413
410
  * @param props properties/attributes
414
411
  */
415
412
  function jsx(tag, props) {
416
- const maybeDummyRef = isKTRef(props.ref) ? props.ref : dummyRef;
413
+ if (isComputed(props.ref)) {
414
+ throw new Error('[kt.js error] Cannot assign a computed value to an element.');
415
+ }
416
+ const maybeDummyRef = isRef(props.ref) ? props.ref : dummyRef;
417
417
  let el;
418
418
  if ('k-if' in props) {
419
419
  const kif = props['k-if'];
420
420
  let condition = kif; // assume boolean by default
421
421
  // Handle reactive k-if
422
- if (isKTRef(kif)) {
422
+ if (isKT(kif)) {
423
423
  kif.addOnChange((newValue, oldValue) => {
424
424
  if (newValue === oldValue) {
425
425
  return;
426
426
  }
427
427
  const oldEl = el;
428
428
  el = newValue ? create(tag, props) : placeholder();
429
- oldEl.replaceWith(el);
429
+ $replaceNode(oldEl, el);
430
430
  maybeDummyRef.value = el;
431
431
  });
432
432
  condition = kif.value;
@@ -507,4 +507,4 @@ function createRedrawable(creator) {
507
507
  return elRef;
508
508
  }
509
509
 
510
- export { $modelOrRef, Fragment, KTRef, h as createElement, createRedrawable, deref, isKTRef, jsx, jsxDEV, jsxs, ref, surfaceRef };
510
+ export { Fragment, h as createElement, createRedrawable, jsx, jsxDEV, jsxs };
@@ -7,13 +7,19 @@ type HTMLTag = keyof HTMLElementTagNameMap;
7
7
  type SVGTag = keyof SVGElementTagNameMap;
8
8
  type MathMLTag = keyof MathMLElementTagNameMap;
9
9
 
10
- type RefChangeHandler<T> = (newValue: T, oldValue: T) => void;
10
+ declare const enum KTReactiveType {
11
+ REF = 1,
12
+ COMPUTED = 2
13
+ }
14
+ type ReactiveChangeHandler<T> = (newValue: T, oldValue: T) => void;
15
+
11
16
  declare class KTRef<T> {
12
17
  /**
13
18
  * Indicates that this is a KTRef instance
14
19
  */
15
- isKT: boolean;
16
- constructor(_value: T, _onChanges: Array<RefChangeHandler<T>>);
20
+ isKT: true;
21
+ ktType: KTReactiveType;
22
+ constructor(_value: T, _onChanges: Array<ReactiveChangeHandler<T>>);
17
23
  /**
18
24
  * If new value and old value are both nodes, the old one will be replaced in the DOM
19
25
  */
@@ -23,8 +29,8 @@ declare class KTRef<T> {
23
29
  * Register a callback when the value changes
24
30
  * @param callback (newValue, oldValue) => xxx
25
31
  */
26
- addOnChange(callback: RefChangeHandler<T>): void;
27
- removeOnChange(callback: RefChangeHandler<T>): boolean;
32
+ addOnChange(callback: ReactiveChangeHandler<T>): void;
33
+ removeOnChange(callback: ReactiveChangeHandler<T>): boolean;
28
34
  }
29
35
 
30
36
  type HTML<T extends (HTMLTag | SVGTag | MathMLTag) & otherstring> = T extends SVGTag
@@ -60,6 +66,12 @@ interface KTBaseAttribute {
60
66
  */
61
67
  'k-model'?: KTRef<any>;
62
68
 
69
+ /**
70
+ * Directly apply html string to `innerHTML`.
71
+ * - Would be reactive if `KTRef` instance is provided
72
+ */
73
+ 'k-html'?: any;
74
+
63
75
  // # normal HTML attributes
64
76
  id?: string;
65
77
  class?: string;
@@ -113,11 +125,11 @@ interface KTBaseAttribute {
113
125
  method?: 'POST' | 'GET' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS' | 'CONNECT' | 'TRACE' | otherstring;
114
126
  }
115
127
 
116
- type KTPrefixedEventHandlers = {
128
+ type KTPrefixedEventAttribute = {
117
129
  [EventName in keyof HTMLElementEventMap as `on:${EventName}`]?: (ev: HTMLElementEventMap[EventName]) => void;
118
130
  };
119
131
 
120
- type KTAttribute = KTBaseAttribute & KTPrefixedEventHandlers;
132
+ type KTAttribute = KTBaseAttribute & KTPrefixedEventAttribute;
121
133
 
122
134
  /**
123
135
  * Create an enhanced HTMLElement.
@@ -129,7 +141,7 @@ type KTAttribute = KTBaseAttribute & KTPrefixedEventHandlers;
129
141
  * ## About
130
142
  * @package @ktjs/core
131
143
  * @author Kasukabe Tsumugi <futami16237@gmail.com>
132
- * @version 0.22.5 (Last Update: 2026.02.03 09:07:21.411)
144
+ * @version 0.24.0 (Last Update: 2026.02.05 12:08:54.164)
133
145
  * @license MIT
134
146
  * @link https://github.com/baendlorel/kt.js
135
147
  * @link https://baendlorel.github.io/ Welcome to my site!