jj 2.5.0 → 2.6.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/README.md +29 -1
- package/SKILL.md +671 -0
- package/lib/bundle.cjs +2031 -0
- package/lib/bundle.cjs.map +1 -0
- package/lib/bundle.d.cts +1782 -0
- package/lib/bundle.d.ts +1782 -1
- package/lib/bundle.global.js +1953 -0
- package/lib/bundle.global.js.map +1 -0
- package/lib/bundle.js +232 -230
- package/lib/bundle.js.map +1 -7
- package/lib/bundle.min.cjs +2 -0
- package/lib/bundle.min.cjs.map +1 -0
- package/lib/bundle.min.d.cts +1782 -0
- package/lib/bundle.min.d.ts +1782 -1
- package/lib/bundle.min.global.js +2 -0
- package/lib/bundle.min.global.js.map +1 -0
- package/lib/bundle.min.js +2 -1
- package/lib/bundle.min.js.map +1 -0
- package/package.json +5 -5
- package/lib/JJD.d.ts +0 -87
- package/lib/JJD.js +0 -119
- package/lib/JJD.js.map +0 -1
- package/lib/JJDF.d.ts +0 -74
- package/lib/JJDF.js +0 -98
- package/lib/JJDF.js.map +0 -1
- package/lib/JJE.d.ts +0 -299
- package/lib/JJE.js +0 -401
- package/lib/JJE.js.map +0 -1
- package/lib/JJET.d.ts +0 -79
- package/lib/JJET.js +0 -114
- package/lib/JJET.js.map +0 -1
- package/lib/JJEx.d.ts +0 -63
- package/lib/JJEx.js +0 -83
- package/lib/JJEx.js.map +0 -1
- package/lib/JJHE.d.ts +0 -109
- package/lib/JJHE.js +0 -136
- package/lib/JJHE.js.map +0 -1
- package/lib/JJN-wrap.d.ts +0 -1
- package/lib/JJN-wrap.js +0 -46
- package/lib/JJN-wrap.js.map +0 -1
- package/lib/JJN.d.ts +0 -126
- package/lib/JJN.js +0 -166
- package/lib/JJN.js.map +0 -1
- package/lib/JJNx.d.ts +0 -126
- package/lib/JJNx.js +0 -157
- package/lib/JJNx.js.map +0 -1
- package/lib/JJSE.d.ts +0 -170
- package/lib/JJSE.js +0 -217
- package/lib/JJSE.js.map +0 -1
- package/lib/JJSR.d.ts +0 -71
- package/lib/JJSR.js +0 -90
- package/lib/JJSR.js.map +0 -1
- package/lib/JJT.d.ts +0 -92
- package/lib/JJT.js +0 -116
- package/lib/JJT.js.map +0 -1
- package/lib/case.d.ts +0 -60
- package/lib/case.js +0 -92
- package/lib/case.js.map +0 -1
- package/lib/components.d.ts +0 -147
- package/lib/components.js +0 -287
- package/lib/components.js.map +0 -1
- package/lib/helpers.d.ts +0 -159
- package/lib/helpers.js +0 -233
- package/lib/helpers.js.map +0 -1
- package/lib/index.d.ts +0 -33
- package/lib/index.js +0 -35
- package/lib/index.js.map +0 -1
- package/lib/internal.d.ts +0 -30
- package/lib/internal.js +0 -35
- package/lib/internal.js.map +0 -1
- package/lib/types.d.ts +0 -65
- package/lib/types.js +0 -2
- package/lib/types.js.map +0 -1
- package/lib/util.d.ts +0 -68
- package/lib/util.js +0 -90
- package/lib/util.js.map +0 -1
- package/llms.txt +0 -214
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ npm i jj
|
|
|
13
13
|
```js
|
|
14
14
|
import { JJHE } from 'jj'
|
|
15
15
|
|
|
16
|
-
JJHE.
|
|
16
|
+
JJHE.create('div')
|
|
17
17
|
.addClass('card')
|
|
18
18
|
.setText('Hello World!')
|
|
19
19
|
.on('click', () => console.log('Hi'))
|
|
@@ -31,6 +31,34 @@ JJHE.fromTag('div')
|
|
|
31
31
|
|
|
32
32
|
**👉 [Visit the full site with tutorials, examples, and API docs](https://alexewerlof.github.io/jj)**
|
|
33
33
|
|
|
34
|
+
## 🤖 AI-Optimized Development
|
|
35
|
+
|
|
36
|
+
JJ is designed for AI-assisted development. Install the skill for intelligent code suggestions:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx skills add alexewerlof/jj
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Once installed, AI agents (GitHub Copilot, Cursor, Claude Code, Windsurf, etc.) will:
|
|
43
|
+
|
|
44
|
+
- Follow JJ's patterns and conventions automatically
|
|
45
|
+
- Know when to use `.ref` for native DOM access
|
|
46
|
+
- Suggest correct framework translations (React/Vue/jQuery/Svelte → JJ)
|
|
47
|
+
- Generate idiomatic, type-safe code
|
|
48
|
+
|
|
49
|
+
The skill definition (`SKILL.md`) is also included in the npm package at `node_modules/jj/SKILL.md`.
|
|
50
|
+
|
|
51
|
+
## ✅ Testing
|
|
52
|
+
|
|
53
|
+
The entire public API is tested thoroghly.
|
|
54
|
+
Tests live in the `test/` folder and mirror the source filenames (e.g., `test/JJE.test.ts` for `src/JJE.ts`) while importing the target from `./src/index.js`.
|
|
55
|
+
|
|
56
|
+
Run tests with:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm test
|
|
60
|
+
```
|
|
61
|
+
|
|
34
62
|
## License
|
|
35
63
|
|
|
36
64
|
MIT
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jj-dom-helpers
|
|
3
|
+
description: >
|
|
4
|
+
Minimal, imperative TypeScript DOM library for AI-assisted development. Use when
|
|
5
|
+
working with DOM manipulation, custom elements, or building lightweight web
|
|
6
|
+
components without frameworks. Triggers on tasks involving direct DOM manipulation,
|
|
7
|
+
Shadow DOM, document fragments, template handling, or converting from React/Vue/jQuery.
|
|
8
|
+
license: MIT
|
|
9
|
+
metadata:
|
|
10
|
+
author: alexewerlof
|
|
11
|
+
version: '1.0.0'
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# JJ: The DOM Library for AI-Assisted Development
|
|
15
|
+
|
|
16
|
+
JJ is a minimal, imperative DOM manipulation library designed for modern web development. It provides small wrapper classes around DOM interfaces with a fluent API for chaining operations. Zero-dependency, TypeScript-first, and optimized for AI-assisted code generation.
|
|
17
|
+
|
|
18
|
+
## Core Principles
|
|
19
|
+
|
|
20
|
+
1. **Imperative by Design**: Direct DOM manipulation without virtual DOM overhead
|
|
21
|
+
2. **Type-Safe**: Full TypeScript support with precise generic types for one-shot LLM correctness
|
|
22
|
+
3. **Chainable**: All public methods support fluent chaining via `.ref` access
|
|
23
|
+
4. **Zero Build**: Works directly in browsers via ESM imports or bundled script tags
|
|
24
|
+
5. **Self-Correcting Errors**: Error messages suggest the correct approach
|
|
25
|
+
|
|
26
|
+
## When to Use This Skill
|
|
27
|
+
|
|
28
|
+
- Building vanilla JavaScript/TypeScript web applications
|
|
29
|
+
- Creating custom web components with Shadow DOM
|
|
30
|
+
- Working with DOM manipulation in a type-safe way
|
|
31
|
+
- Developing lightweight UIs without React/Vue/Angular
|
|
32
|
+
- Implementing template-based rendering
|
|
33
|
+
- Managing document fragments and element creation
|
|
34
|
+
|
|
35
|
+
## Main Classes
|
|
36
|
+
|
|
37
|
+
- **JJET**: Wraps a DOM `EventTrigger` elements. Base wrapper for all other wrappers.
|
|
38
|
+
- **JJN**: Wraps a DOM `Node`. Extends `JJET`.
|
|
39
|
+
- **JJNx**: Abstract wrapper for `Node`s that can have children and query selectors (`Element`, `Document`, `DocumentFragment`). Extends `JJN`.
|
|
40
|
+
- **JJD**: Wraps a DOM `Document`. Extends `JJNx`.
|
|
41
|
+
- **JJE**: Wraps a DOM `Element`. Generic element wrapper. Extends `JJNx`.
|
|
42
|
+
- **JJEx**: Abstract wrapper for `Element`s that have dataset support (`HTMLElement`, `SVGElement`). Extends `JJE`.
|
|
43
|
+
- **JJHE**: Wraps a DOM `HTMLElement`. Extends `JJEx`.
|
|
44
|
+
- **JJT**: Wraps a DOM `Text`. Text node operations. Extends `JJN`.
|
|
45
|
+
- **JJSE**: Wraps a DOM `SVGElements`. SVG-specific operations. Extends `JJEx`.
|
|
46
|
+
- **JJSR**: Wraps a DOM `ShadowRoot`. Shadow DOM operations. Extends `JJDF`.
|
|
47
|
+
- **JJDF**: Wraps a DOM `DocumentFragment`. Fragment operations. Extends `JJNx`.
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
The easiest way to use JJ is the `h()` hyperscript helper:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { doc, h, JJDF } from 'jj'
|
|
55
|
+
|
|
56
|
+
// Create elements with h(tagName, attributes, ...children)
|
|
57
|
+
const nav = h(
|
|
58
|
+
'nav',
|
|
59
|
+
{ class: 'main-nav', 'aria-label': 'Main navigation' },
|
|
60
|
+
h('a', { href: '/', title: 'Go to homepage' }, 'Home'),
|
|
61
|
+
h('a', { href: '/about', title: 'Learn more about us' }, 'About'),
|
|
62
|
+
h('a', { href: 'https://github.com', target: '_blank', rel: 'noopener' }, 'GitHub'),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
// Append to document body
|
|
66
|
+
doc.body.ref.append(nav.ref)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
For batch DOM operations, use `JJDF` (DocumentFragment) to avoid multiple reflows:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import { doc, h, JJDF } from 'jj'
|
|
73
|
+
|
|
74
|
+
// Create a fragment for batch operations
|
|
75
|
+
const frag = JJDF.create()
|
|
76
|
+
|
|
77
|
+
const items = ['Apple', 'Banana', 'Cherry']
|
|
78
|
+
frag.addChild(...items.map((item) => h('li', { class: 'fruit-item' }, item)))
|
|
79
|
+
|
|
80
|
+
// Single DOM update - much faster than appending one by one
|
|
81
|
+
doc.find('ul.fruit-list')?.addChild(frag.ref)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
If you need to map and append the children, there's a direct, shorter and more readable way to do it:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import { doc, h } from 'jj'
|
|
88
|
+
|
|
89
|
+
const items = ['Apple', 'Banana', 'Cherry']
|
|
90
|
+
|
|
91
|
+
// Single DOM update - much shorter
|
|
92
|
+
doc.find('ul.fruit-list')?.addChildMap(items, (item) => h('li', { class: 'fruit-item' }, item))
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Combine both patterns for complex UIs:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import { doc, h, JJDF } from 'jj'
|
|
99
|
+
|
|
100
|
+
// Build a card component
|
|
101
|
+
const card = h(
|
|
102
|
+
'article',
|
|
103
|
+
{ class: 'card' },
|
|
104
|
+
h('header', null, h('h2', { class: 'card-title' }, 'Welcome')),
|
|
105
|
+
h('p', { class: 'card-body' }, 'This is the card content.'),
|
|
106
|
+
h('footer', null, h('a', { href: '/details', class: 'btn' }, 'Learn More')),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
doc.body.addChild(card.ref)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Accessing the Native Node: The `.ref` Property
|
|
113
|
+
|
|
114
|
+
**Critical**: Every JJ wrapper exposes `.ref` to access the underlying DOM node:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
const wrapped = JJD.from(document).create('div')
|
|
118
|
+
const nativeElement: HTMLDivElement = wrapped.ref
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Tip**: If you are sure an element exists (e.g. static HTML), pass `true` as the second argument to `.find()` to throw if missing:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// Throws TypeError if #app is missing
|
|
125
|
+
const app = doc.find('#app', true)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Use `.ref` when:
|
|
129
|
+
|
|
130
|
+
- Accessing properties not exposed by JJ wrappers
|
|
131
|
+
- Passing to third-party APIs
|
|
132
|
+
- Using native DOM methods directly
|
|
133
|
+
|
|
134
|
+
## Querying the DOM
|
|
135
|
+
|
|
136
|
+
For the JJ-specific querying patterns (`find`, `findAll`, `closest`) and guidance on when to use each, see [guides/query.md](guides/query.md).
|
|
137
|
+
|
|
138
|
+
## Key Patterns & Conventions
|
|
139
|
+
|
|
140
|
+
### 1. ESM Import Style
|
|
141
|
+
|
|
142
|
+
All imports use `.js` extension even in TypeScript files:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import { h, doc, JJD } from './index.js'
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
This is required by the project's ESM configuration. Never use `.ts` extensions in imports.
|
|
149
|
+
|
|
150
|
+
### 2. Fluent Wrapper API
|
|
151
|
+
|
|
152
|
+
Most operations return wrapped instances for chaining:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// Incorrect: accessing .ref too early
|
|
156
|
+
const element = doc.create('div').ref
|
|
157
|
+
element.textContent = 'hello'
|
|
158
|
+
|
|
159
|
+
// Correct: chain operations before accessing .ref
|
|
160
|
+
doc.create('div').setText('hello').addClass('greeting').addChild(childElement)
|
|
161
|
+
|
|
162
|
+
// Use .run() to execute code in the context of the wrapper
|
|
163
|
+
doc.create('div').run(function () {
|
|
164
|
+
// `this` refers to the JJ wrapper instance
|
|
165
|
+
this.addClass('active')
|
|
166
|
+
console.log(this.ref) // Access native element
|
|
167
|
+
})
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Common chainable operations:
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
el.addClass('active').removeClass('disabled').toggleClass('visible')
|
|
174
|
+
el.attr('data-age', 42).prop('disabled', false)
|
|
175
|
+
el.style('background-color', 'blue').style('padding', '10px')
|
|
176
|
+
// ARIA helpers
|
|
177
|
+
el.setAria('hidden', 'true').rmAria('label')
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### 3. Type-Safe Element Creation
|
|
181
|
+
|
|
182
|
+
JJ leverages TypeScript generics to ensure type safety and prevent LLM hallucinations:
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
// create() infers the correct type automatically
|
|
186
|
+
const input = JJHE.create('input')
|
|
187
|
+
// input.ref is HTMLInputElement, so input.ref.value exists
|
|
188
|
+
|
|
189
|
+
const div = JJHE.create('div')
|
|
190
|
+
// div.ref is HTMLDivElement, so div.ref.value would be a type error
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Incorrect**: Generic on wrong method
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
const input = doc.create<HTMLInputElement>('input')
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Correct**: Use `create()` for type inference
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
const input = doc.create('input') // Correctly typed as JJHE<HTMLInputElement>
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### 4. Custom Elements
|
|
206
|
+
|
|
207
|
+
JJ is not opnionated about custom elements. You should use the native lifecycle callbacks:
|
|
208
|
+
Custom element lifecycle callbacks include (all of which can be `async`):
|
|
209
|
+
|
|
210
|
+
- `connectedCallback()`: Called each time the element is added to the document. The specification recommends that, as far as possible, developers should implement custom element setup in this callback instead of the constructor. JJ provides `ShadowMaster` for managing Shadow DOM configuration (templates and styles) with support for eager/lazy loading.
|
|
211
|
+
- `disconnectedCallback()`: Called each time the element is removed from the document.
|
|
212
|
+
- `connectedMoveCallback()`: When defined, this is called instead of `connectedCallback()` and `disconnectedCallback()` each time the element is moved to a different place in the DOM via `Element.moveBefore()`. Use this to avoid running initialization/cleanup code in the `connectedCallback()` and `disconnectedCallback()` callbacks when the element is not actually being added to or removed from the DOM.
|
|
213
|
+
- `adoptedCallback()`: Called each time the element is moved to a new document.
|
|
214
|
+
- `attributeChangedCallback()`: Called when attributes are changed, added, removed, or replaced. See Responding to attribute changes for more details about this callback. JJ provides `attr2prop()` for converting attributes to properties.
|
|
215
|
+
|
|
216
|
+
On top of those, JJ also provides `registerComponent()` for registering custom elements and awaiting till they are defined.
|
|
217
|
+
|
|
218
|
+
**Basic pattern** - Create a shared `ShadowMaster` instance outside the class for caching:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
import { attr2prop, fetchCss, fetchHtml, JJHE, registerComponent, ShadowMaster } from 'jj'
|
|
222
|
+
|
|
223
|
+
// ShadowMaster is created OUTSIDE the class - config is resolved once and cached
|
|
224
|
+
const sm = ShadowMaster.create()
|
|
225
|
+
.setTemplate(fetchHtml(import.meta.resolve('./my-component.html')))
|
|
226
|
+
.addStyles(fetchCss(import.meta.resolve('./my-component.css')))
|
|
227
|
+
|
|
228
|
+
export class MyComponent extends HTMLElement {
|
|
229
|
+
static observedAttributes = ['title', 'count']
|
|
230
|
+
|
|
231
|
+
static register() {
|
|
232
|
+
return registerComponent('my-component', MyComponent)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Private state
|
|
236
|
+
#title = ''
|
|
237
|
+
#count = 0
|
|
238
|
+
|
|
239
|
+
// Bridge attributes to properties
|
|
240
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
241
|
+
attr2prop(this, name, oldValue, newValue)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Getters/setters for observed attributes
|
|
245
|
+
get title() {
|
|
246
|
+
return this.#title
|
|
247
|
+
}
|
|
248
|
+
set title(value) {
|
|
249
|
+
this.#title = value
|
|
250
|
+
this.#render()
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async connectedCallback() {
|
|
254
|
+
// Initialize shadow root with resolved config
|
|
255
|
+
this.jjRoot = JJHE.from(this).initShadow('open', await sm.getResolved())
|
|
256
|
+
|
|
257
|
+
// Access elements inside Shadow DOM
|
|
258
|
+
this.jjRoot.shadow.find('#inc').on('click', () => this.#update(1))
|
|
259
|
+
this.jjRoot.shadow.find('#dec').on('click', () => this.#update(-1))
|
|
260
|
+
this.#render()
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
#update(delta) {
|
|
264
|
+
this.#count += delta
|
|
265
|
+
this.#render()
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
#render() {
|
|
269
|
+
this.jjRoot?.shadow.find('#title').setText(this.#title)
|
|
270
|
+
this.jjRoot?.shadow.find('#count').setText(this.#count)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**Template sources** - `setTemplate()` accepts various inputs:
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
// From external HTML file (eager loading)
|
|
279
|
+
sm.setTemplate(fetchHtml('./template.html'))
|
|
280
|
+
|
|
281
|
+
// From inline string
|
|
282
|
+
sm.setTemplate('<div id="root"><slot></slot></div>')
|
|
283
|
+
|
|
284
|
+
// From existing <template> element
|
|
285
|
+
sm.setTemplate(document.querySelector<HTMLTemplateElement>('#my-template')?.innerHTML)
|
|
286
|
+
|
|
287
|
+
// From existing element's HTML
|
|
288
|
+
sm.setTemplate(document.getElementById('template-source')?.outerHTML)
|
|
289
|
+
|
|
290
|
+
// Lazy loading with function (called on first getResolved())
|
|
291
|
+
sm.setTemplate(() => fetchHtml('./lazy-template.html'))
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**Style sources** - `addStyles()` accepts multiple styles:
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
// From external CSS file
|
|
298
|
+
sm.addStyles(fetchCss('./styles.css'))
|
|
299
|
+
|
|
300
|
+
// From inline CSS string
|
|
301
|
+
sm.addStyles('.container { padding: 1rem; }')
|
|
302
|
+
|
|
303
|
+
// Multiple sources at once
|
|
304
|
+
sm.addStyles('p { color: red; }', fetchCss('./base.css'), fetchCss('./theme.css'))
|
|
305
|
+
|
|
306
|
+
// Lazy loading (function called on first getResolved())
|
|
307
|
+
sm.addStyles(() => fetchCss('./lazy-styles.css'))
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**Register multiple components** with `Promise.all()`:
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
import { MyComponent } from './my-component.js'
|
|
314
|
+
import { OtherComponent } from './other-component.js'
|
|
315
|
+
|
|
316
|
+
await Promise.all([MyComponent.register(), OtherComponent.register()])
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### 5. Event Handling
|
|
320
|
+
|
|
321
|
+
Attach events on wrapped elements:
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
button.on('click', (e) => {
|
|
325
|
+
console.log('clicked')
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
// This works for delegation as well
|
|
329
|
+
container.on('click', (e) => {
|
|
330
|
+
if (e.target.matches('.item')) {
|
|
331
|
+
// handle
|
|
332
|
+
}
|
|
333
|
+
})
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### 6. Case Conversion Utilities
|
|
337
|
+
|
|
338
|
+
The library provides case conversion helpers:
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
import { keb2cam, keb2pas, pas2keb } from 'jj'
|
|
342
|
+
|
|
343
|
+
keb2cam('my-component') // 'myComponent'
|
|
344
|
+
keb2pas('my-component') // 'MyComponent'
|
|
345
|
+
pas2keb('MyComponent') // 'my-component'
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### 7. Error Handling Philosophy
|
|
349
|
+
|
|
350
|
+
The library uses explicit error handling:
|
|
351
|
+
|
|
352
|
+
- No silent failures
|
|
353
|
+
- Throw descriptive errors for invalid states
|
|
354
|
+
- Use type guards (`isElement`, `isHTMLElement`, etc.)
|
|
355
|
+
- Validate inputs at boundaries
|
|
356
|
+
|
|
357
|
+
### 8. Styling, CSS & Resources
|
|
358
|
+
|
|
359
|
+
Inject styles using helper functions:
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
import { fetchCss, fetchStyle, cssToStyle, addLinkPre } from 'jj'
|
|
363
|
+
|
|
364
|
+
// Fetch and inject external CSS
|
|
365
|
+
document.adoptedStyleSheets.push(await fetchCss('/styles/theme.css'))
|
|
366
|
+
|
|
367
|
+
const cssPath = import.meta.resolve('./styles/theme.css')
|
|
368
|
+
// Preload/Prefetch resources ASAP
|
|
369
|
+
addLinkPre(cssPath, 'preload', 'style')
|
|
370
|
+
// Helper for Constructable Stylesheets loading lazily
|
|
371
|
+
const sheet = await fetchStyle(cssPath)
|
|
372
|
+
document.adoptedStyleSheets = [sheet]
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### 9. Accessing Native DOM with `.ref`
|
|
376
|
+
|
|
377
|
+
Know when to use `.ref`:
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
// Stay wrapped for chaining
|
|
381
|
+
element.addClass('active').setText('Updated')
|
|
382
|
+
|
|
383
|
+
// Access .ref for native DOM operations
|
|
384
|
+
element.ref.focus()
|
|
385
|
+
element.ref.scrollIntoView()
|
|
386
|
+
|
|
387
|
+
// Pass .ref to third-party APIs
|
|
388
|
+
thirdPartyLib.init(element.ref)
|
|
389
|
+
|
|
390
|
+
// Use .ref for properties not exposed by JJ
|
|
391
|
+
console.log(input.ref.value)
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
## Framework Translation Patterns for LLMs
|
|
395
|
+
|
|
396
|
+
### React to JJ
|
|
397
|
+
|
|
398
|
+
**React**: State-driven component re-renders on state change
|
|
399
|
+
|
|
400
|
+
```tsx
|
|
401
|
+
const [count, setCount] = useState(0)
|
|
402
|
+
return <button onClick={() => setCount(count + 1)}>{count}</button>
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
**JJ**: Direct DOM mutation and event binding
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
let count = 0
|
|
409
|
+
const btn = JJD.from(document).create('button').setText('0')
|
|
410
|
+
btn.on('click', () => {
|
|
411
|
+
count++
|
|
412
|
+
btn.setText(String(count))
|
|
413
|
+
})
|
|
414
|
+
document.body.appendChild(btn.ref)
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### jQuery to JJ
|
|
418
|
+
|
|
419
|
+
**jQuery**: Chainable DOM manipulation with `$()` selector
|
|
420
|
+
|
|
421
|
+
```javascript
|
|
422
|
+
$('.box').addClass('active').css('color', 'red')
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
**JJ**: Similar chainable API with type safety
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
JJD.from(document).find('.box').addClass('active').style('color', 'red')
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Vue to JJ
|
|
432
|
+
|
|
433
|
+
**Vue**: Template-driven, two-way binding
|
|
434
|
+
|
|
435
|
+
```vue
|
|
436
|
+
<div>
|
|
437
|
+
<input v-model="message" />
|
|
438
|
+
<p>{{ message }}</p>
|
|
439
|
+
</div>
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
**JJ**: Imperative setup with manual event binding
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
const input = JJD.from(document).create('input')
|
|
446
|
+
const p = JJD.from(document).create('p')
|
|
447
|
+
input.on('input', (e) => {
|
|
448
|
+
p.setText((e.target as HTMLInputElement).value)
|
|
449
|
+
})
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Svelte to JJ
|
|
453
|
+
|
|
454
|
+
**Svelte**: Reactive variables and automatic re-renders
|
|
455
|
+
|
|
456
|
+
```svelte
|
|
457
|
+
<script>
|
|
458
|
+
let count = 0
|
|
459
|
+
</script>
|
|
460
|
+
<button on:click={() => count++}>{count}</button>
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
**JJ**: Manual DOM updates on events
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
let count = 0
|
|
467
|
+
const btn = JJD.from(document).create('button').setText('0')
|
|
468
|
+
btn.on('click', () => {
|
|
469
|
+
count++
|
|
470
|
+
btn.setText(String(count))
|
|
471
|
+
})
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
## Common Tasks
|
|
475
|
+
|
|
476
|
+
### Creating a Custom Element
|
|
477
|
+
|
|
478
|
+
See section **4. Custom Elements** above for the full pattern. Here's a minimal example:
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
import { attr2prop, fetchCss, fetchHtml, JJHE, registerComponent, ShadowMaster } from 'jj'
|
|
482
|
+
|
|
483
|
+
const sm = ShadowMaster.create()
|
|
484
|
+
.setTemplate(fetchHtml(import.meta.resolve('./simple-counter.html')))
|
|
485
|
+
.addStyles(fetchCss(import.meta.resolve('./simple-counter.css')))
|
|
486
|
+
|
|
487
|
+
export class SimpleCounter extends HTMLElement {
|
|
488
|
+
static register() {
|
|
489
|
+
return registerComponent('simple-counter', SimpleCounter)
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
#count = 0
|
|
493
|
+
|
|
494
|
+
async connectedCallback() {
|
|
495
|
+
this.jjRoot = JJHE.from(this).initShadow('open', await sm.getResolved())
|
|
496
|
+
this.jjRoot.shadow.find('#inc').on('click', () => this.#update(1))
|
|
497
|
+
this.jjRoot.shadow.find('#dec').on('click', () => this.#update(-1))
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
#update(delta) {
|
|
501
|
+
this.#count += delta
|
|
502
|
+
this.jjRoot.shadow.find('#count').setText(this.#count)
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### DOM Manipulation
|
|
508
|
+
|
|
509
|
+
```typescript
|
|
510
|
+
import { JJD } from 'jj'
|
|
511
|
+
|
|
512
|
+
const doc = JJD.from(document)
|
|
513
|
+
|
|
514
|
+
// Create and append elements
|
|
515
|
+
const container = doc.create('div').addClass('container')
|
|
516
|
+
|
|
517
|
+
const h1 = doc.create('h1').setText('Title')
|
|
518
|
+
const p = doc.create('p').setText('Content')
|
|
519
|
+
|
|
520
|
+
container.ref.appendChild(h1.ref)
|
|
521
|
+
container.ref.appendChild(p.ref)
|
|
522
|
+
doc.body.appendChild(container.ref)
|
|
523
|
+
|
|
524
|
+
// Query and modify
|
|
525
|
+
const element = doc.find('.container')
|
|
526
|
+
if (element) {
|
|
527
|
+
element.addClass('active').attr('data-loaded', 'true')
|
|
528
|
+
}
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
### Working with Templates
|
|
532
|
+
|
|
533
|
+
For non-component template usage, use `JJET` to work with `<template>` elements:
|
|
534
|
+
|
|
535
|
+
```typescript
|
|
536
|
+
import { doc, JJET } from 'jj'
|
|
537
|
+
|
|
538
|
+
// Get a template element from the DOM
|
|
539
|
+
const templateEl = doc.find<HTMLTemplateElement>('#item-template')
|
|
540
|
+
const wrapped = JJET.from(templateEl.ref)
|
|
541
|
+
|
|
542
|
+
// Clone the template content
|
|
543
|
+
const items = ['Apple', 'Banana', 'Cherry']
|
|
544
|
+
items.forEach((item) => {
|
|
545
|
+
const clone = wrapped.createInstance()
|
|
546
|
+
clone.find('#name')?.setText(item)
|
|
547
|
+
doc.find('#list')?.addChild(clone.ref)
|
|
548
|
+
})
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
For component templates, prefer `ShadowMaster` with `fetchHtml()` (see section 4).
|
|
552
|
+
|
|
553
|
+
## Code Style Notes
|
|
554
|
+
|
|
555
|
+
- **No semicolons** unless required (ASI style)
|
|
556
|
+
- **Single quotes** for strings
|
|
557
|
+
- **Explicit types** for public APIs
|
|
558
|
+
- **JSDoc comments** for exported functions
|
|
559
|
+
- **Fluent interfaces** where possible
|
|
560
|
+
- **Small, focused classes** (single responsibility)
|
|
561
|
+
|
|
562
|
+
## Testing
|
|
563
|
+
|
|
564
|
+
Tests are written using Node's built-in test runner and live in the root `test/` folder (mirroring source filenames):
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
import { describe, it } from 'node:test'
|
|
568
|
+
import assert from 'node:assert'
|
|
569
|
+
|
|
570
|
+
describe('JJD', () => {
|
|
571
|
+
it('wraps document correctly', () => {
|
|
572
|
+
const wrapped = JJD.from(document)
|
|
573
|
+
assert(wrapped instanceof JJD)
|
|
574
|
+
})
|
|
575
|
+
})
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
Run tests with:
|
|
579
|
+
|
|
580
|
+
```bash
|
|
581
|
+
npm test
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
## Building
|
|
585
|
+
|
|
586
|
+
Build TypeScript to ESM + type definitions:
|
|
587
|
+
|
|
588
|
+
```bash
|
|
589
|
+
npm run build
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
This produces:
|
|
593
|
+
|
|
594
|
+
- `lib/*.js` - ESM modules
|
|
595
|
+
- `lib/*.d.ts` - Type definitions
|
|
596
|
+
- `lib/bundle.js` - Browser bundle
|
|
597
|
+
- `lib/bundle.min.js` - Minified bundle
|
|
598
|
+
|
|
599
|
+
## Documentation
|
|
600
|
+
|
|
601
|
+
Auto-generated API docs using TypeDoc:
|
|
602
|
+
|
|
603
|
+
```bash
|
|
604
|
+
npm run doc
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
Output: `doc/` folder with HTML documentation
|
|
608
|
+
|
|
609
|
+
## References
|
|
610
|
+
|
|
611
|
+
- [Tutorial Files](./www/tutorial/) - Step-by-step guides
|
|
612
|
+
- [Example Components](./www/components/) - Real usage examples
|
|
613
|
+
- [Source Code](./src/) - Implementation details
|
|
614
|
+
- [API Documentation](./doc/) - Generated API reference
|
|
615
|
+
|
|
616
|
+
## Common Gotchas
|
|
617
|
+
|
|
618
|
+
1. **Accessing the Native Node**: Always use `.ref` to access the underlying DOM node for operations not exposed by JJ
|
|
619
|
+
2. **Event Listeners**: Use `.on()` and `.off()` for proper cleanup. Event handlers are automatically bound to the JJ\* instance, so `this` refers to the wrapper, not the DOM element. Use `function` instead of arrow functions (`=>`) for `.bind()` to work correctly. Use `this.ref` to access the native element.
|
|
620
|
+
3. **Type Safety**: Use `JJHE.create()` for automatic type inference instead of generic parameters
|
|
621
|
+
4. **Fragments**: Use JJDF for batch operations before appending
|
|
622
|
+
5. **Error Messages**: Read them carefully—they suggest the correct approach
|
|
623
|
+
|
|
624
|
+
## Anti-patterns to Avoid
|
|
625
|
+
|
|
626
|
+
❌ **Don't** use `.ts` in imports
|
|
627
|
+
❌ **Don't** access `.ref` before chaining operations
|
|
628
|
+
❌ **Don't** ignore TypeScript errors about DOM types
|
|
629
|
+
❌ **Don't** use generic type parameters when `JJHE.create()` gives better inference
|
|
630
|
+
❌ **Don't** manually manage Shadow DOM when `ShadowMaster` can handle it
|
|
631
|
+
❌ **Don't** forget to await async initializations
|
|
632
|
+
❌ **Don't** forget `.ref` when passing to native APIs or third-party libraries
|
|
633
|
+
|
|
634
|
+
✅ **Do** use `.js` extensions in TypeScript imports
|
|
635
|
+
✅ **Do** chain operations before accessing `.ref`
|
|
636
|
+
✅ **Do** use `JJHE.create()` for element creation with type inference
|
|
637
|
+
✅ **Do** leverage the fluent API
|
|
638
|
+
✅ **Do** use `ShadowMaster` for Shadow DOM components
|
|
639
|
+
✅ **Do** handle errors explicitly
|
|
640
|
+
✅ **Do** use `.ref` when accessing native properties like `.value`, `.checked`, etc.
|
|
641
|
+
|
|
642
|
+
## Installation
|
|
643
|
+
|
|
644
|
+
```bash
|
|
645
|
+
npm install jj
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
Or use directly in browser:
|
|
649
|
+
|
|
650
|
+
```html
|
|
651
|
+
<script type="module">
|
|
652
|
+
import { JJD } from 'https://unpkg.com/jj/lib/bundle.js'
|
|
653
|
+
</script>
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
## For Best Results with LLMs
|
|
657
|
+
|
|
658
|
+
1. When asking an LLM to help with JJ code, provide a snippet of the desired DOM structure
|
|
659
|
+
2. Reference the framework translation patterns above when converting from React, Vue, Svelte, or jQuery
|
|
660
|
+
3. Ask the LLM to use `.ref` when accessing native properties not exposed by JJ
|
|
661
|
+
4. Expect type errors to be self-documenting—they guide toward the correct API
|
|
662
|
+
5. Remind the LLM that JJ is imperative, not declarative—no virtual DOM or automatic re-rendering
|
|
663
|
+
6. Don't overuse the `h()` function. First and foremost try to keep as much of the static layout of the page in the HTML.
|
|
664
|
+
7. Upon the start of the app, store references to key static elements using `JJHE.find('#element-id', true)`
|
|
665
|
+
|
|
666
|
+
## Resources
|
|
667
|
+
|
|
668
|
+
- **GitHub**: https://github.com/alexewerlof/jj
|
|
669
|
+
- **npm**: https://www.npmjs.com/package/jj
|
|
670
|
+
- **Documentation**: https://jj.rocks/doc/
|
|
671
|
+
- **Examples**: https://jj.rocks/examples/
|