@ktjs/core 0.18.10 → 0.19.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 +359 -284
- package/dist/index.d.ts +22 -15
- package/dist/index.iife.js +104 -89
- package/dist/index.legacy.js +103 -96
- package/dist/index.mjs +104 -90
- package/dist/jsx/index.d.ts +22 -15
- package/dist/jsx/index.mjs +100 -86
- package/dist/jsx/jsx-runtime.d.ts +19 -12
- package/dist/jsx/jsx-runtime.mjs +99 -85
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -1,378 +1,453 @@
|
|
|
1
|
-
# @ktjs/core
|
|
2
|
-
|
|
3
1
|
<img src="https://raw.githubusercontent.com/baendlorel/kt.js/dev/.assets/ktjs-0.0.1.svg" alt="KT.js Logo" width="150"/>
|
|
4
2
|
|
|
5
|
-
[](https://www.npmjs.com/package/kt.js) [](https://github.com/baendlorel/kt.js/blob/main/LICENSE)
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
[CHANGLOG✨](CHANGELOG.md)
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
## What's New (v0.19.x)
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
- Release `0.19.0` (2026-01-31): build & packaging fixes, TypeScript and lint cleanups, MUI-focused fixes (JSX handling, `jsxImportSource` set to `@ktjs/core`, radio/checkbox/TextField fixes), and repo cleanup (removed alias detection, moved shared utilities into `shared`). See the full details in the CHANGELOG.
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
> Note: This framework is still under development. APIs, type declarations, and other parts **may change frequently**. If you use it, please watch for updates in the near future. Feel free to mail me if you have any questions!
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
KT.js is a tiny DOM utility focused on direct DOM manipulation. It favors not forcing re-renders and aims to keep DOM updates to the absolute minimum for maximum performance.
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
For more awesome packages, check out [my homepage💛](https://baendlorel.github.io/?repoType=npm)
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
- **
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
- **k-if directive**: Conditional element creation with `k-if` attribute
|
|
30
|
-
- Array children support for seamless list rendering
|
|
31
|
-
- **KTFor Component**: Efficient list rendering with key-based optimization (v0.16.0)
|
|
32
|
-
- Returns Comment anchor with `__kt_for_list__` array storing rendered elements
|
|
33
|
-
- Auto-appends list items when anchor is added via `applyContent`
|
|
34
|
-
- Key-based DOM reuse with customizable key function (default: identity)
|
|
35
|
-
- Type-safe with full TypeScript generics support
|
|
36
|
-
- **KTAsync Component**: Handle async components with ease
|
|
37
|
-
- Automatic handling of Promise-based components
|
|
38
|
-
- Seamless integration with JSX/TSX
|
|
39
|
-
- Fallback placeholder during async loading
|
|
40
|
-
- Type-safe async component support
|
|
41
|
-
- **Redraw Mechanism**: Fine-grained control over component updates
|
|
42
|
-
- Update props and children selectively
|
|
43
|
-
- Efficient replacement strategy
|
|
44
|
-
- Works with both native elements and function components
|
|
45
|
-
- **Ref Enhancement**: Change event binding support for `ref` (v0.18.1)
|
|
46
|
-
- New methods `addOnChange` and `removeOnChange` for listening to value changes
|
|
47
|
-
- Automatically calls registered callbacks when `ref.value` is updated
|
|
48
|
-
- When both old and new values are DOM nodes, automatically replaces old node with new one in the DOM
|
|
49
|
-
- **DOM Utilities**: Helper functions for common DOM operations
|
|
50
|
-
- Native method caching for performance
|
|
51
|
-
- Symbol-based private properties for internal state
|
|
52
|
-
- **Type-Safe**: Complete TypeScript definitions
|
|
53
|
-
- Accurate HTMLElement type inference
|
|
54
|
-
- Event handler type hints with proper event types
|
|
55
|
-
- Support for custom attributes and properties
|
|
56
|
-
- **ES5 Compatible**: Transpiled to ES5 for maximum browser compatibility
|
|
57
|
-
- **Zero Dependencies**: Fully self-contained implementation
|
|
58
|
-
|
|
59
|
-
## Installation
|
|
17
|
+
## Architecture
|
|
18
|
+
|
|
19
|
+
KT.js is now a **monorepo** containing multiple packages:
|
|
20
|
+
|
|
21
|
+
- **[kt.js](./packages/kt.js)**: Main entry package that re-exports all functionality
|
|
22
|
+
- **[@ktjs/core](./packages/core)**: Core DOM manipulation utilities and the `h` function. SX/TSX support with full TypeScript integration (included in kt.js package)
|
|
23
|
+
- **[@ktjs/router](./packages/router)**: Client-side routing with navigation guards (not included in kt.js package)
|
|
24
|
+
- **[@ktjs/shortcuts](./packages/shortcuts)**: Convenient shortcut functions for common operations
|
|
25
|
+
|
|
26
|
+
You can install the full package or individual packages as needed:
|
|
60
27
|
|
|
61
28
|
```bash
|
|
62
|
-
|
|
63
|
-
|
|
29
|
+
# Install the main package (includes core + jsx + shortcuts)
|
|
30
|
+
pnpm add kt.js
|
|
64
31
|
|
|
65
|
-
|
|
32
|
+
# Or install individual packages
|
|
33
|
+
pnpm add @ktjs/core # Core DOM utilities (independent)
|
|
34
|
+
pnpm add @ktjs/router # Client-side router (independent)
|
|
35
|
+
pnpm add @ktjs/shortcuts # Shortcuts (requires @ktjs/core)
|
|
36
|
+
```
|
|
66
37
|
|
|
67
|
-
|
|
38
|
+
## Philosophy
|
|
68
39
|
|
|
69
|
-
|
|
70
|
-
import { h } from '@ktjs/core';
|
|
40
|
+
As a web framework, repeatedly creating a large number of variables and objects is unacceptable. So I created KT.js.
|
|
71
41
|
|
|
72
|
-
|
|
73
|
-
const div = h('div', { class: 'container' }, 'Hello World');
|
|
42
|
+
KT.js follows one rule: **full control of DOM and avoid unnecessary repainting**.
|
|
74
43
|
|
|
75
|
-
|
|
76
|
-
const input = h('input', {
|
|
77
|
-
type: 'text',
|
|
78
|
-
placeholder: 'Enter text',
|
|
79
|
-
value: 'initial',
|
|
80
|
-
});
|
|
44
|
+
## Key Features
|
|
81
45
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
46
|
+
- **Monorepo Architecture**: Modular packages that can be installed independently or together
|
|
47
|
+
- **Tiny Bundle Size**: Minimal runtime overhead with aggressive tree-shaking
|
|
48
|
+
- **`h` function**: Create DOM elements with a simple, flexible API
|
|
49
|
+
- Shortcut functions for all HTML elements (`div`, `span`, `button`, etc.)
|
|
50
|
+
- Event handlers with `on:<eventName>` syntax or function attributes
|
|
51
|
+
- Full TypeScript support with intelligent type inference
|
|
52
|
+
- **JSX/TSX Support**: Full JSX syntax support with TypeScript integration
|
|
53
|
+
- Zero virtual DOM - JSX compiles directly to `h()` function calls
|
|
54
|
+
- Full HTML element type inference (`<button>` returns `HTMLButtonElement`)
|
|
55
|
+
- Support for `on:click` event handler syntax
|
|
56
|
+
- `redraw()` method for controlled component updates (v0.11+)
|
|
57
|
+
- `k-if` directive for conditional rendering (v0.14.6+)
|
|
58
|
+
- Array children support for seamless `.map()` integration (v0.14.1+)
|
|
59
|
+
- **List Rendering**: Efficient list rendering with `KTFor` component (v0.16.0+)
|
|
60
|
+
- Comment anchor with `__kt_for_list__` array property
|
|
61
|
+
- Key-based DOM reuse for minimal updates
|
|
62
|
+
- Auto-appends list items when anchor added to parent
|
|
63
|
+
- **Async Components**: Built-in support for Promise-based components
|
|
64
|
+
- `KTAsync` component for handling async operations
|
|
65
|
+
- Automatic placeholder management during loading
|
|
66
|
+
- Seamless integration with JSX/TSX syntax
|
|
67
|
+
- **Client-Side Router** (separate package):
|
|
68
|
+
- Hash-based routing only (simplified from v0.14.7+)
|
|
69
|
+
- Async navigation guards with Promise support
|
|
70
|
+
- Dynamic route parameters and query string parsing
|
|
71
|
+
- RouterView component for declarative routing
|
|
72
|
+
- Pure routing logic - no rendering, no dependencies
|
|
73
|
+
- **Shortcuts & Utilities**:
|
|
74
|
+
- `withDefaults`: Wrap element creation functions with default properties
|
|
75
|
+
- Convenient shorthand functions for common operations
|
|
76
|
+
- Form helpers and layout utilities
|
|
77
|
+
- **Full ES5 Compatibility**: Works in IE9+ and all modern browsers
|
|
78
|
+
- Transpiled to ES5 with no modern syntax
|
|
79
|
+
- Optional minimal Promise polyfill for older environments
|
|
80
|
+
- **Shared Runtime**: Efficient code sharing across packages with zero overhead
|
|
81
|
+
|
|
82
|
+
## Getting started
|
|
83
|
+
|
|
84
|
+
Install via package managers:
|
|
88
85
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
86
|
+
```bash
|
|
87
|
+
npm install kt.js
|
|
88
|
+
# or
|
|
89
|
+
pnpm add kt.js
|
|
92
90
|
```
|
|
93
91
|
|
|
94
|
-
|
|
92
|
+
```ts
|
|
93
|
+
import { h, div } from 'kt.js';
|
|
95
94
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// on: prefixed attribute (event handler)
|
|
100
|
-
const button1 = h(
|
|
101
|
-
'button',
|
|
102
|
-
{
|
|
103
|
-
'on:click': () => alert('Clicked!'),
|
|
104
|
-
},
|
|
105
|
-
'Button 1',
|
|
106
|
-
);
|
|
95
|
+
const container = div('container', [div('header'), div('body', 'something'), div('footer')]);
|
|
96
|
+
const app = h('section', { id: 'app' }, container);
|
|
97
|
+
```
|
|
107
98
|
|
|
108
|
-
|
|
109
|
-
const button2 = h(
|
|
110
|
-
'button',
|
|
111
|
-
{
|
|
112
|
-
click: (e) => console.log('Event:', e),
|
|
113
|
-
'data-id': '123', // Regular attribute
|
|
114
|
-
},
|
|
115
|
-
'Button 2',
|
|
116
|
-
);
|
|
99
|
+
This will create the following DOM structure:
|
|
117
100
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
101
|
+
```html
|
|
102
|
+
<section id="app">
|
|
103
|
+
<div class="container">
|
|
104
|
+
<div class="header"></div>
|
|
105
|
+
<div class="body">something</div>
|
|
106
|
+
<div class="footer"></div>
|
|
107
|
+
</div>
|
|
108
|
+
</section>
|
|
123
109
|
```
|
|
124
110
|
|
|
125
|
-
### JSX/TSX
|
|
111
|
+
### Using JSX/TSX
|
|
126
112
|
|
|
127
|
-
|
|
128
|
-
import { h } from '@ktjs/core';
|
|
113
|
+
KT.js now has full JSX support! With the `@ktjs/jsx` package (included in the main `kt.js` package), you can write components using familiar JSX syntax:
|
|
129
114
|
|
|
130
|
-
|
|
115
|
+
**TypeScript Configuration** (`tsconfig.json`):
|
|
116
|
+
|
|
117
|
+
```json
|
|
131
118
|
{
|
|
132
119
|
"compilerOptions": {
|
|
133
120
|
"jsx": "react-jsx",
|
|
134
|
-
"jsxImportSource": "
|
|
121
|
+
"jsxImportSource": "kt.js"
|
|
135
122
|
}
|
|
136
123
|
}
|
|
137
|
-
|
|
138
|
-
// Use JSX syntax
|
|
139
|
-
const App = () => (
|
|
140
|
-
<div class="app">
|
|
141
|
-
<h1>Hello KT.js</h1>
|
|
142
|
-
<button on:click={() => alert('Hi')}>Click me</button>
|
|
143
|
-
</div>
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
// Function components
|
|
147
|
-
const Greeting = ({ name }: { name: string }) => (
|
|
148
|
-
<div class="greeting">Hello, {name}!</div>
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
const app = <Greeting name="World" />;
|
|
152
124
|
```
|
|
153
125
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
The `k-if` directive allows conditional element creation:
|
|
126
|
+
**Basic JSX Example**:
|
|
157
127
|
|
|
158
128
|
```tsx
|
|
159
|
-
import {
|
|
160
|
-
|
|
161
|
-
// Element will only be created if condition is true
|
|
162
|
-
const isVisible = true;
|
|
163
|
-
const element = <div k-if={isVisible}>This will be rendered</div>;
|
|
129
|
+
import { jsx } from 'kt.js';
|
|
164
130
|
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
const hidden = <div k-if={isHidden}>This will NOT be rendered</div>;
|
|
168
|
-
// hidden will be undefined/null
|
|
131
|
+
function Counter() {
|
|
132
|
+
const count = 0;
|
|
169
133
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
<div k-if={isLoggedIn}>
|
|
175
|
-
<p>Welcome, {user.name}!</p>
|
|
176
|
-
<button>Logout</button>
|
|
134
|
+
return (
|
|
135
|
+
<div class="counter">
|
|
136
|
+
<h1>Counter: {count}</h1>
|
|
137
|
+
<button on:click={() => console.log('Clicked!')}>Increment</button>
|
|
177
138
|
</div>
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
<button>Login</button>
|
|
181
|
-
</div>
|
|
182
|
-
</div>
|
|
183
|
-
);
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
**Note**: `k-if` is evaluated **once** at element creation time. It's not reactive - if you need dynamic visibility, use CSS or manually recreate the element.
|
|
139
|
+
);
|
|
140
|
+
}
|
|
187
141
|
|
|
188
|
-
|
|
142
|
+
// JSX compiles to direct h() function calls - no virtual DOM!
|
|
143
|
+
const counterElement = <Counter />;
|
|
144
|
+
```
|
|
189
145
|
|
|
190
|
-
|
|
146
|
+
**Event Handling with @ Syntax**:
|
|
191
147
|
|
|
192
148
|
```tsx
|
|
193
|
-
|
|
149
|
+
function App() {
|
|
150
|
+
const handleClick = () => alert('Button clicked!');
|
|
194
151
|
|
|
195
|
-
|
|
196
|
-
|
|
152
|
+
return (
|
|
153
|
+
<div>
|
|
154
|
+
<button on:click={handleClick}>Click me</button>
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
```
|
|
197
159
|
|
|
198
|
-
|
|
199
|
-
const Counter = ({ count = 0 }: { count?: number }) => (
|
|
200
|
-
<div>
|
|
201
|
-
<div>Count: {count}</div>
|
|
202
|
-
<button on:click={() => element.redraw({ count: count + 1 })}>Increment</button>
|
|
203
|
-
</div>
|
|
204
|
-
);
|
|
160
|
+
**Type Safety**:
|
|
205
161
|
|
|
206
|
-
|
|
162
|
+
```tsx
|
|
163
|
+
// TypeScript knows this is an HTMLButtonElement
|
|
164
|
+
const button: HTMLButtonElement = <button>Click</button>;
|
|
207
165
|
|
|
208
|
-
//
|
|
209
|
-
|
|
166
|
+
// TypeScript knows this is an HTMLInputElement
|
|
167
|
+
const input: HTMLInputElement = <input type="text" value="hello" />;
|
|
210
168
|
|
|
211
|
-
//
|
|
212
|
-
const div =
|
|
213
|
-
div.redraw(undefined, 'New content');
|
|
169
|
+
// TypeScript provides autocomplete for HTML attributes
|
|
170
|
+
const div: HTMLDivElement = <div className="container" id="main" />;
|
|
214
171
|
```
|
|
215
172
|
|
|
216
|
-
|
|
173
|
+
**Important Notes**:
|
|
217
174
|
|
|
218
|
-
|
|
175
|
+
- KT.js JSX has **no Fragment support** - we don't have a Fragment concept
|
|
176
|
+
- JSX compiles directly to `h()` function calls - **zero virtual DOM overhead**
|
|
177
|
+
- Use `on:click` syntax for event handlers to avoid conflicts with existing attributes
|
|
178
|
+
- All JSX elements have proper HTML element type inference in TypeScript
|
|
179
|
+
- Use `k-if` attribute for conditional rendering (v0.14.6+)
|
|
180
|
+
- Children can be arrays for easy `.map()` integration (v0.14.1+)
|
|
219
181
|
|
|
220
|
-
|
|
221
|
-
import { KTFor } from '@ktjs/core';
|
|
182
|
+
**Conditional Rendering with k-if** (v0.14.6+):
|
|
222
183
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
184
|
+
```tsx
|
|
185
|
+
import { jsx } from 'kt.js';
|
|
186
|
+
|
|
187
|
+
function UserProfile({ user, isLoggedIn }: { user: any; isLoggedIn: boolean }) {
|
|
188
|
+
return (
|
|
189
|
+
<div>
|
|
190
|
+
<h1>Profile</h1>
|
|
191
|
+
{/* Element only created if condition is true */}
|
|
192
|
+
<div k-if={isLoggedIn}>
|
|
193
|
+
<p>Welcome, {user.name}!</p>
|
|
194
|
+
<button>Logout</button>
|
|
195
|
+
</div>
|
|
196
|
+
{/* Element only created if condition is true */}
|
|
197
|
+
<div k-if={!isLoggedIn}>
|
|
198
|
+
<p>Please log in</p>
|
|
199
|
+
<button>Login</button>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
);
|
|
227
203
|
}
|
|
204
|
+
```
|
|
228
205
|
|
|
229
|
-
|
|
230
|
-
{ id: 1, text: 'Buy milk', done: false },
|
|
231
|
-
{ id: 2, text: 'Write code', done: true },
|
|
232
|
-
];
|
|
233
|
-
|
|
234
|
-
// Create optimized list with key-based reuse
|
|
235
|
-
const todoList = (
|
|
236
|
-
<KTFor
|
|
237
|
-
list={todos}
|
|
238
|
-
key={(item) => item.id} // Optional, defaults to identity function
|
|
239
|
-
map={(item, index) => (
|
|
240
|
-
<div class={`todo ${item.done ? 'done' : ''}`}>
|
|
241
|
-
<input type="checkbox" checked={item.done} />
|
|
242
|
-
<span>{item.text}</span>
|
|
243
|
-
<button on:click={() => deleteTodo(item.id)}>Delete</button>
|
|
244
|
-
</div>
|
|
245
|
-
)}
|
|
246
|
-
/>
|
|
247
|
-
);
|
|
206
|
+
**Array Children Support** (v0.14.1+):
|
|
248
207
|
|
|
249
|
-
|
|
250
|
-
|
|
208
|
+
```tsx
|
|
209
|
+
import { jsx } from 'kt.js';
|
|
210
|
+
|
|
211
|
+
function TodoList({ todos }: { todos: string[] }) {
|
|
212
|
+
return (
|
|
213
|
+
<div>
|
|
214
|
+
<h2>Todo List</h2>
|
|
215
|
+
<ul>
|
|
216
|
+
{/* Map arrays directly as children */}
|
|
217
|
+
{todos.map((todo) => (
|
|
218
|
+
<li>{todo}</li>
|
|
219
|
+
))}
|
|
220
|
+
</ul>
|
|
221
|
+
</div>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
251
224
|
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
|
|
225
|
+
// Mix mapped elements with other elements
|
|
226
|
+
function MixedList({ items }: { items: string[] }) {
|
|
227
|
+
return (
|
|
228
|
+
<ul>
|
|
229
|
+
<li>Header Item</li>
|
|
230
|
+
{items.map((item) => (
|
|
231
|
+
<li>{item}</li>
|
|
232
|
+
))}
|
|
233
|
+
<li>Footer Item</li>
|
|
234
|
+
</ul>
|
|
235
|
+
);
|
|
236
|
+
}
|
|
255
237
|
```
|
|
256
238
|
|
|
257
|
-
|
|
239
|
+
### Async Components with KTAsync
|
|
258
240
|
|
|
259
|
-
-
|
|
260
|
-
- When appended via `applyContent`, anchor and all list items are added to DOM
|
|
261
|
-
- Uses key-based diff to reuse DOM nodes on `redraw()`
|
|
262
|
-
- Only adds/removes/moves nodes that changed
|
|
241
|
+
KT.js provides built-in support for async components through the `KTAsync` component:
|
|
263
242
|
|
|
264
|
-
|
|
243
|
+
```tsx
|
|
244
|
+
import { KTAsync, ref } from 'kt.js';
|
|
245
|
+
|
|
246
|
+
// Define an async component that returns a Promise<HTMLElement>
|
|
247
|
+
const AsyncUserCard = function () {
|
|
248
|
+
return fetch('/api/user')
|
|
249
|
+
.then((res) => res.json())
|
|
250
|
+
.then((user) => (
|
|
251
|
+
<div class="user-card">
|
|
252
|
+
<h2>{user.name}</h2>
|
|
253
|
+
<p>{user.email}</p>
|
|
254
|
+
</div>
|
|
255
|
+
));
|
|
256
|
+
};
|
|
265
257
|
|
|
266
|
-
|
|
258
|
+
// Use KTAsync to handle the async component
|
|
259
|
+
function App() {
|
|
260
|
+
return (
|
|
261
|
+
<div class="app">
|
|
262
|
+
<h1>User Profile</h1>
|
|
263
|
+
<KTAsync component={AsyncUserCard} />
|
|
264
|
+
</div>
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
267
|
|
|
268
|
-
|
|
268
|
+
// The component starts with a placeholder comment node
|
|
269
|
+
// When the Promise resolves, it automatically replaces with the actual element
|
|
270
|
+
```
|
|
269
271
|
|
|
270
|
-
|
|
271
|
-
import { h } from '@ktjs/core';
|
|
272
|
-
|
|
273
|
-
// Map arrays directly as children
|
|
274
|
-
const items = ['Apple', 'Banana', 'Orange'];
|
|
275
|
-
const list = (
|
|
276
|
-
<ul>
|
|
277
|
-
{items.map((item) => (
|
|
278
|
-
<li>{item}</li>
|
|
279
|
-
))}
|
|
280
|
-
</ul>
|
|
281
|
-
);
|
|
272
|
+
**How KTAsync works:**
|
|
282
273
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
<ul>
|
|
288
|
-
{todos.map((todo) => (
|
|
289
|
-
<li>{todo}</li>
|
|
290
|
-
))}
|
|
291
|
-
<li>
|
|
292
|
-
<button>Add More</button>
|
|
293
|
-
</li>
|
|
294
|
-
</ul>
|
|
295
|
-
</div>
|
|
296
|
-
);
|
|
297
|
-
````
|
|
274
|
+
1. Creates a placeholder comment node (`ktjs-suspense-placeholder`) immediately
|
|
275
|
+
2. Calls your component function (which should return a `Promise<HTMLElement>` or `HTMLElement`)
|
|
276
|
+
3. When the Promise resolves, automatically replaces the placeholder with the resolved element
|
|
277
|
+
4. If your component returns a non-Promise value, it's used directly without async handling
|
|
298
278
|
|
|
299
|
-
|
|
279
|
+
**Example with dynamic updates:**
|
|
300
280
|
|
|
301
|
-
```
|
|
302
|
-
|
|
281
|
+
```tsx
|
|
282
|
+
const DynamicContent = function () {
|
|
283
|
+
const count = ref(0);
|
|
284
|
+
const container = (
|
|
285
|
+
<div>
|
|
286
|
+
<p>Count: {count}</p>
|
|
287
|
+
<button on:click={() => count.value++}>Increment</button>
|
|
288
|
+
</div>
|
|
289
|
+
);
|
|
303
290
|
|
|
304
|
-
//
|
|
305
|
-
const AsyncComponent = () => {
|
|
291
|
+
// Simulate async data loading
|
|
306
292
|
return new Promise<HTMLElement>((resolve) => {
|
|
307
|
-
setTimeout(() =>
|
|
308
|
-
const element = h('div', { class: 'loaded' }, 'Content loaded!');
|
|
309
|
-
resolve(element);
|
|
310
|
-
}, 1000);
|
|
293
|
+
setTimeout(() => resolve(container), 500);
|
|
311
294
|
});
|
|
312
295
|
};
|
|
313
296
|
|
|
314
|
-
//
|
|
315
|
-
const
|
|
316
|
-
h('h1', {}, 'Loading...'),
|
|
317
|
-
KTAsync({ component: AsyncComponent }),
|
|
318
|
-
]);
|
|
319
|
-
|
|
320
|
-
// With JSX/TSX
|
|
321
|
-
const App = () => (
|
|
297
|
+
// Usage
|
|
298
|
+
const app = (
|
|
322
299
|
<div>
|
|
323
|
-
<h1>Loading...</h1>
|
|
324
|
-
<KTAsync component={
|
|
300
|
+
<h1>Loading async content...</h1>
|
|
301
|
+
<KTAsync component={DynamicContent} />
|
|
325
302
|
</div>
|
|
326
303
|
);
|
|
304
|
+
```
|
|
327
305
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
306
|
+
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:
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
const button = btn(
|
|
310
|
+
{
|
|
311
|
+
dblclick: '22',
|
|
312
|
+
'on:dblclick': function trueHandler() {
|
|
313
|
+
/* ... */
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
'Click me',
|
|
336
317
|
);
|
|
318
|
+
|
|
319
|
+
// This is equivalent to:
|
|
320
|
+
const button = btn(undefined, 'Click me');
|
|
321
|
+
button.setAttribute('dblclick', '22');
|
|
322
|
+
button.addEventListener('click', () => alert('Clicked!'));
|
|
323
|
+
button.addEventListener('dblclick', function trueHandler() {
|
|
324
|
+
/* ... */
|
|
325
|
+
});
|
|
337
326
|
```
|
|
338
327
|
|
|
339
|
-
|
|
328
|
+
### Working with CSS-in-JS Libraries
|
|
340
329
|
|
|
341
|
-
|
|
342
|
-
- When the Promise resolves, it automatically replaces the placeholder with the actual element
|
|
343
|
-
- If the component returns a non-Promise value, it's used directly
|
|
344
|
-
- No manual DOM manipulation needed - just return a Promise from your component
|
|
330
|
+
KT.js works seamlessly with CSS-in-JS libraries like `@emotion/css`:
|
|
345
331
|
|
|
346
|
-
|
|
332
|
+
```ts
|
|
333
|
+
import { css } from '@emotion/css';
|
|
334
|
+
import { h, div } from 'kt.js';
|
|
347
335
|
|
|
348
|
-
|
|
336
|
+
const className = css`
|
|
337
|
+
color: red;
|
|
338
|
+
font-size: 20px;
|
|
339
|
+
`;
|
|
349
340
|
|
|
350
|
-
|
|
341
|
+
// Pass class name as attribute
|
|
342
|
+
h('div', { class: className }, 'Styled text');
|
|
351
343
|
|
|
352
|
-
|
|
344
|
+
// Or as the first string argument
|
|
345
|
+
div(className, 'Styled text');
|
|
346
|
+
```
|
|
353
347
|
|
|
354
|
-
|
|
355
|
-
- `attributes` (object, optional): Element attributes and event handlers. **Must be an object** - string className shorthand is not supported.
|
|
356
|
-
- `content` (string | HTMLElement | Array, optional): Element content
|
|
348
|
+
### Using Shortcuts with Default Values
|
|
357
349
|
|
|
358
|
-
|
|
350
|
+
The `withDefaults` function allows you to create element factories with predefined properties:
|
|
359
351
|
|
|
360
|
-
|
|
352
|
+
```ts
|
|
353
|
+
import { withDefaults, div, button } from 'kt.js';
|
|
361
354
|
|
|
362
|
-
|
|
355
|
+
// Create a styled div factory
|
|
356
|
+
const card = withDefaults(div, { class: 'card' });
|
|
357
|
+
const blueCard = withDefaults(card, { style: 'background: blue' });
|
|
363
358
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
- Event handler types with proper event object types
|
|
359
|
+
// Use them
|
|
360
|
+
const myCard = card('card-body', 'Content'); // <div class="card"><div class="card-body">Content</div></div>
|
|
361
|
+
const myBlueCard = blueCard('title', 'Blue!'); // <div class="card" style="background: blue"><div class="title">Blue!</div></div>
|
|
362
|
+
```
|
|
369
363
|
|
|
370
|
-
##
|
|
364
|
+
## Router
|
|
365
|
+
|
|
366
|
+
The router is available as a separate package `@ktjs/router`:
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
import { createRouter } from '@ktjs/router';
|
|
370
|
+
import { div, h1 } from 'kt.js';
|
|
371
|
+
|
|
372
|
+
const router = createRouter({
|
|
373
|
+
routes: [
|
|
374
|
+
{
|
|
375
|
+
path: '/',
|
|
376
|
+
name: 'home',
|
|
377
|
+
beforeEnter: (to) => {
|
|
378
|
+
// Render your page here
|
|
379
|
+
document.getElementById('app')!.innerHTML = '';
|
|
380
|
+
document.getElementById('app')!.appendChild(div({}, [h1({}, 'Home Page')]));
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
path: '/user/:id',
|
|
385
|
+
name: 'user',
|
|
386
|
+
beforeEnter: (to) => {
|
|
387
|
+
// Route-specific guard and rendering
|
|
388
|
+
console.log('Entering user page');
|
|
389
|
+
document.getElementById('app')!.innerHTML = '';
|
|
390
|
+
document.getElementById('app')!.appendChild(div({}, [h1({}, `User ${to.params.id}`)]));
|
|
391
|
+
return true;
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
],
|
|
395
|
+
beforeEach: async (to, from) => {
|
|
396
|
+
// Global navigation guard - return false to block navigation
|
|
397
|
+
console.log('Navigating to:', to.path);
|
|
398
|
+
return true;
|
|
399
|
+
},
|
|
400
|
+
afterEach: (to) => {
|
|
401
|
+
// Called after successful navigation
|
|
402
|
+
document.title = to.name || to.path;
|
|
403
|
+
},
|
|
404
|
+
onError: (error) => {
|
|
405
|
+
console.error('Router error:', error);
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// Navigate programmatically
|
|
410
|
+
router.push('/user/123');
|
|
411
|
+
router.push('/user/456?page=2');
|
|
412
|
+
|
|
413
|
+
// Navigate by route name
|
|
414
|
+
router.push({ name: 'user', params: { id: '789' } });
|
|
415
|
+
|
|
416
|
+
// Get current route
|
|
417
|
+
console.log(router.current?.path, router.current?.params, router.current?.query);
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Router Features
|
|
371
421
|
|
|
372
|
-
-
|
|
373
|
-
-
|
|
374
|
-
-
|
|
375
|
-
-
|
|
422
|
+
- **Hash-based Routing Only** (v0.14.7+): Uses URL hash for client-side navigation (`#/path`)
|
|
423
|
+
- **Dynamic Parameters**: Support for dynamic route segments (`/user/:id`)
|
|
424
|
+
- **Query Strings**: Automatic parsing of query parameters (`?key=value`)
|
|
425
|
+
- **Named Routes**: Navigate using route names instead of paths
|
|
426
|
+
- **Async Navigation Guards**:
|
|
427
|
+
- `beforeEach`: Global guard before navigation (async)
|
|
428
|
+
- `beforeEnter`: Per-route guard (can also be used for rendering, async)
|
|
429
|
+
- `afterEach`: Global hook after navigation
|
|
430
|
+
- All guards support Promise-based async operations
|
|
431
|
+
- Guards can return `false` to cancel, string/object to redirect
|
|
432
|
+
- `GuardLevel` for fine-grained control over guard execution
|
|
433
|
+
- **Error Handling**: `onError` and `onNotFound` callbacks
|
|
434
|
+
- **Optimized Performance**: Pre-flattened routes and efficient matching algorithm
|
|
435
|
+
- **Zero Dependencies**: Fully self-contained router implementation (does not require `@ktjs/core` for runtime, only for TypeScript types)
|
|
436
|
+
- **Pure Routing**: No rendering logic - you control the DOM
|
|
437
|
+
- **Automatic Initialization**: Router auto-initializes on creation (v0.14.7+)
|
|
438
|
+
|
|
439
|
+
## Browser Compatibility
|
|
440
|
+
|
|
441
|
+
KT.js is transpiled to ES5 and works in all modern browsers as well as legacy browsers including IE9+.
|
|
442
|
+
|
|
443
|
+
### Promise Polyfill
|
|
444
|
+
|
|
445
|
+
For environments without native `Promise` support (like IE).
|
|
446
|
+
|
|
447
|
+
```js
|
|
448
|
+
import 'some promise polyfill'; // Will fallback to sync version if Promise is not available
|
|
449
|
+
import { h, div, createRouter } from 'kt.js';
|
|
450
|
+
```
|
|
376
451
|
|
|
377
452
|
## License
|
|
378
453
|
|