kt.js 0.9.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -45,13 +45,17 @@ KT.js follows one rule: **full control of DOM and avoid unnecessary repainting**
45
45
  - **Tiny Bundle Size**: Minimal runtime overhead with aggressive tree-shaking
46
46
  - **`h` function**: Create DOM elements with a simple, flexible API
47
47
  - Shortcut functions for all HTML elements (`div`, `span`, `button`, etc.)
48
- - Event handlers with `@<eventName>` syntax or function attributes
48
+ - Event handlers with `on:<eventName>` syntax or function attributes
49
49
  - Full TypeScript support with intelligent type inference
50
50
  - **JSX/TSX Support**: Full JSX syntax support with TypeScript integration
51
51
  - Zero virtual DOM - JSX compiles directly to `h()` function calls
52
52
  - Full HTML element type inference (`<button>` returns `HTMLButtonElement`)
53
53
  - Support for `@click` event handler syntax
54
54
  - No Fragment support - KT.js doesn't have a Fragment concept
55
+ - **Async Components**: Built-in support for Promise-based components
56
+ - `KTAsync` component for handling async operations
57
+ - Automatic placeholder management during loading
58
+ - Seamless integration with JSX/TSX syntax
55
59
  - **Client-Side Router** (separate package):
56
60
  - Hash-based routing with dynamic parameters
57
61
  - Navigation guards with async/sync auto-adaptation
@@ -64,7 +68,6 @@ KT.js follows one rule: **full control of DOM and avoid unnecessary repainting**
64
68
  - **Full ES5 Compatibility**: Works in IE9+ and all modern browsers
65
69
  - Transpiled to ES5 with no modern syntax
66
70
  - Optional minimal Promise polyfill for older environments
67
- - **`ktnull`**: A special value for filtering null/undefined while preserving native DOM behavior
68
71
  - **Shared Runtime**: Efficient code sharing across packages with zero overhead
69
72
 
70
73
  ## Getting started
@@ -106,7 +109,7 @@ KT.js now has full JSX support! With the `@ktjs/jsx` package (included in the ma
106
109
  {
107
110
  "compilerOptions": {
108
111
  "jsx": "react-jsx",
109
- "jsxImportSource": "@ktjs/jsx"
112
+ "jsxImportSource": "kt.js"
110
113
  }
111
114
  }
112
115
  ```
@@ -122,9 +125,7 @@ function Counter() {
122
125
  return (
123
126
  <div class="counter">
124
127
  <h1>Counter: {count}</h1>
125
- <button @click={() => console.log('Clicked!')}>
126
- Increment
127
- </button>
128
+ <button on:click={() => console.log('Clicked!')}>Increment</button>
128
129
  </div>
129
130
  );
130
131
  }
@@ -141,8 +142,7 @@ function App() {
141
142
 
142
143
  return (
143
144
  <div>
144
- <button @click={handleClick}>Click me</button>
145
- <button onclick={handleClick}>Also works</button>
145
+ <button on:click={handleClick}>Click me</button>
146
146
  </div>
147
147
  );
148
148
  }
@@ -168,14 +168,80 @@ const div: HTMLDivElement = <div className="container" id="main" />;
168
168
  - Use `@click` syntax for event handlers to avoid conflicts with existing attributes
169
169
  - All JSX elements have proper HTML element type inference in TypeScript
170
170
 
171
+ ### Async Components with KTAsync
172
+
173
+ KT.js provides built-in support for async components through the `KTAsync` component:
174
+
175
+ ```tsx
176
+ import { KTAsync, ref } from 'kt.js';
177
+
178
+ // Define an async component that returns a Promise<HTMLElement>
179
+ const AsyncUserCard = function () {
180
+ return fetch('/api/user')
181
+ .then((res) => res.json())
182
+ .then((user) => (
183
+ <div class="user-card">
184
+ <h2>{user.name}</h2>
185
+ <p>{user.email}</p>
186
+ </div>
187
+ ));
188
+ };
189
+
190
+ // Use KTAsync to handle the async component
191
+ function App() {
192
+ return (
193
+ <div class="app">
194
+ <h1>User Profile</h1>
195
+ <KTAsync component={AsyncUserCard} />
196
+ </div>
197
+ );
198
+ }
199
+
200
+ // The component starts with a placeholder comment node
201
+ // When the Promise resolves, it automatically replaces with the actual element
202
+ ```
203
+
204
+ **How KTAsync works:**
205
+
206
+ 1. Creates a placeholder comment node (`ktjs-suspense-placeholder`) immediately
207
+ 2. Calls your component function (which should return a `Promise<HTMLElement>` or `HTMLElement`)
208
+ 3. When the Promise resolves, automatically replaces the placeholder with the resolved element
209
+ 4. If your component returns a non-Promise value, it's used directly without async handling
210
+
211
+ **Example with dynamic updates:**
212
+
213
+ ```tsx
214
+ const DynamicContent = function () {
215
+ const count = ref(0);
216
+ const container = (
217
+ <div>
218
+ <p>Count: {count}</p>
219
+ <button on:click={() => count.value++}>Increment</button>
220
+ </div>
221
+ );
222
+
223
+ // Simulate async data loading
224
+ return new Promise<HTMLElement>((resolve) => {
225
+ setTimeout(() => resolve(container), 500);
226
+ });
227
+ };
228
+
229
+ // Usage
230
+ const app = (
231
+ <div>
232
+ <h1>Loading async content...</h1>
233
+ <KTAsync component={DynamicContent} />
234
+ </div>
235
+ );
236
+ ```
237
+
171
238
  If you give a function in attributes, it will be treated as an event listener, and the key will be considered as the event name. `@<eventName>` will also be considered as the handler to avoid conflicts with existing attributes:
172
239
 
173
240
  ```ts
174
241
  const button = btn(
175
242
  {
176
- click: () => alert('Clicked!'),
177
243
  dblclick: '22',
178
- '@dblclick': function trueHandler() {
244
+ 'on:dblclick': function trueHandler() {
179
245
  /* ... */
180
246
  },
181
247
  },
@@ -301,26 +367,6 @@ console.log(router.current?.path, router.current?.params, router.current?.query)
301
367
  - **Zero Dependencies**: Fully self-contained router implementation (does not require `@ktjs/core` for runtime, only for TypeScript types)
302
368
  - **Pure Routing**: No rendering logic - you control the DOM
303
369
 
304
- ## `ktnull`
305
-
306
- `ktnull`, assigned by `Object.create(null)`, is a falsy value.
307
- It is used for filtering, you can do like this:
308
-
309
- ```ts
310
- import { div, ktnull } from 'kt.js';
311
- const list = div('', [false ? div('', 'item 1') : ktnull, div('', 'Item 2'), undefined]);
312
- ```
313
-
314
- Then it will create:
315
-
316
- ```html
317
- <div>
318
- <!-- there won't be <div>Item 1</div> -->
319
- <div>Item 2</div>
320
- undefined
321
- </div>
322
- ```
323
-
324
370
  ## Browser Compatibility
325
371
 
326
372
  KT.js is transpiled to ES5 and works in all modern browsers as well as legacy browsers including IE9+.
@@ -10,12 +10,7 @@ const emptyPromiseHandler = () => ({});
10
10
  if (typeof Promise === 'undefined') {
11
11
  window.Promise = { resolve: emptyPromiseHandler, reject: emptyPromiseHandler };
12
12
  }
13
-
14
- /**
15
- * This is a `falsy` value used to indicate "no node" in `h` function.
16
- * - It's an object, so it's guaranteed to be unique and no need for polyfill of `symbol`.
17
- */
18
- const ktnull = Object.create(null);
13
+ const $isThenable = (o) => typeof o === 'object' && o !== null && 'then' in o && typeof o.then === 'function';
19
14
 
20
15
  /**
21
16
  * & Remove `bind` because it is shockingly slower than wrapper
@@ -26,8 +21,7 @@ const originAppend = HTMLElement.prototype.append;
26
21
  const $append = // for ie 9/10/11
27
22
  typeof originAppend === 'function'
28
23
  ? function (...args) {
29
- const nodes = args.filter((a) => a !== ktnull);
30
- return originAppend.apply(this, nodes);
24
+ return originAppend.apply(this, args);
31
25
  }
32
26
  : function (...nodes) {
33
27
  if (nodes.length < 50) {
@@ -36,7 +30,7 @@ const $append = // for ie 9/10/11
36
30
  if (typeof node === 'string') {
37
31
  $appendChild.call(this, document.createTextNode(node));
38
32
  }
39
- else if (node !== ktnull) {
33
+ else {
40
34
  $appendChild.call(this, node);
41
35
  }
42
36
  }
@@ -48,7 +42,7 @@ const $append = // for ie 9/10/11
48
42
  if (typeof node === 'string') {
49
43
  $appendChild.call(fragment, document.createTextNode(node));
50
44
  }
51
- else if (node !== ktnull) {
45
+ else {
52
46
  $appendChild.call(fragment, node);
53
47
  }
54
48
  }
@@ -155,12 +149,35 @@ function applyAttr(element, attr) {
155
149
  }
156
150
  }
157
151
 
158
- function apd(element, content) {
159
- if (content && content.isKT) {
160
- $append.call(element, content.value);
152
+ function apdSingle(element, c) {
153
+ if (typeof c === 'object' && c !== null && 'isKT' in c) {
154
+ $append.call(element, c.value);
155
+ }
156
+ else {
157
+ $append.call(element, c);
158
+ }
159
+ }
160
+ function apd(element, c) {
161
+ if ($isThenable(c)) {
162
+ c.then((r) => apd(element, r));
163
+ }
164
+ else if ($isArray(c)) {
165
+ for (let i = 0; i < c.length; i++) {
166
+ // & might be thenable here too
167
+ const ci = c[i];
168
+ if ($isThenable(ci)) {
169
+ const comment = document.createComment('ktjs-promise-placeholder');
170
+ $append.call(element, comment);
171
+ ci.then((awaited) => comment.replaceWith(awaited));
172
+ }
173
+ else {
174
+ apdSingle(element, ci);
175
+ }
176
+ }
161
177
  }
162
178
  else {
163
- $append.call(element, content);
179
+ // & here is thened, so must be a simple elementj
180
+ apdSingle(element, c);
164
181
  }
165
182
  }
166
183
  function applyContent(element, content) {
@@ -184,7 +201,7 @@ function applyContent(element, content) {
184
201
  * ## About
185
202
  * @package @ktjs/core
186
203
  * @author Kasukabe Tsumugi <futami16237@gmail.com>
187
- * @version 0.9.0 (Last Update: 2025.12.29 11:09:49.983)
204
+ * @version 0.10.1 (Last Update: 2025.12.30 14:45:59.803)
188
205
  * @license MIT
189
206
  * @link https://github.com/baendlorel/kt.js
190
207
  * @link https://baendlorel.github.io/ Welcome to my site!
@@ -193,7 +210,7 @@ function applyContent(element, content) {
193
210
  */
194
211
  const h = ((tag, attr = '', content = '') => {
195
212
  if (typeof tag !== 'string') {
196
- $throw('__func__ tagName must be a string.');
213
+ $throw('tagName must be a string.');
197
214
  }
198
215
  // * start creating the element
199
216
  const element = document.createElement(tag);
@@ -237,13 +254,12 @@ function jsx(tag, props, ..._metadata) {
237
254
  /**
238
255
  * Fragment support - returns an array of children
239
256
  * Note: kt.js doesn't have a real Fragment concept,
240
- * so we return ktnull for empty fragments or flatten children
241
257
  */
242
258
  function Fragment(props) {
243
259
  window.__ktjs__.throws("kt.js doesn't have a Fragment concept");
244
260
  // const { children } = props || {};
245
261
  // if (!children) {
246
- // return ktnull;
262
+ // return ;
247
263
  // }
248
264
  // // If single child, return it directly
249
265
  // if (!Array.isArray(children)) {
@@ -254,13 +270,11 @@ function Fragment(props) {
254
270
  // const wrapper = document.createElement('div');
255
271
  // wrapper.setAttribute('data-kt-fragment', 'true');
256
272
  // children.forEach((child) => {
257
- // if (child && child !== ktnull) {
258
273
  // if (typeof child === 'string') {
259
274
  // wrapper.appendChild(document.createTextNode(child));
260
275
  // } else if (child instanceof HTMLElement) {
261
276
  // wrapper.appendChild(child);
262
277
  // }
263
- // }
264
278
  // });
265
279
  // return wrapper;
266
280
  }
@@ -268,8 +282,8 @@ function Fragment(props) {
268
282
  * JSX Development runtime - same as jsx but with additional dev checks
269
283
  */
270
284
  const jsxDEV = (...args) => {
271
- console.log('JSX DEV called:', ...args);
272
- console.log('chilren', args[1]?.children);
285
+ // console.log('JSX DEV called:', ...args);
286
+ // console.log('children', (args[1] as any)?.children);
273
287
  return jsx(...args);
274
288
  };
275
289
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kt.js",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "author": {
5
5
  "name": "Kasukabe Tsumugi",
6
6
  "email": "futami16237@gmail.com"
@@ -41,7 +41,7 @@
41
41
  ],
42
42
  "license": "MIT",
43
43
  "dependencies": {
44
- "@ktjs/core": "0.9.0",
44
+ "@ktjs/core": "0.10.1",
45
45
  "@ktjs/shortcuts": "0.7.3"
46
46
  },
47
47
  "scripts": {