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.
Files changed (77) hide show
  1. package/README.md +29 -1
  2. package/SKILL.md +671 -0
  3. package/lib/bundle.cjs +2031 -0
  4. package/lib/bundle.cjs.map +1 -0
  5. package/lib/bundle.d.cts +1782 -0
  6. package/lib/bundle.d.ts +1782 -1
  7. package/lib/bundle.global.js +1953 -0
  8. package/lib/bundle.global.js.map +1 -0
  9. package/lib/bundle.js +232 -230
  10. package/lib/bundle.js.map +1 -7
  11. package/lib/bundle.min.cjs +2 -0
  12. package/lib/bundle.min.cjs.map +1 -0
  13. package/lib/bundle.min.d.cts +1782 -0
  14. package/lib/bundle.min.d.ts +1782 -1
  15. package/lib/bundle.min.global.js +2 -0
  16. package/lib/bundle.min.global.js.map +1 -0
  17. package/lib/bundle.min.js +2 -1
  18. package/lib/bundle.min.js.map +1 -0
  19. package/package.json +5 -5
  20. package/lib/JJD.d.ts +0 -87
  21. package/lib/JJD.js +0 -119
  22. package/lib/JJD.js.map +0 -1
  23. package/lib/JJDF.d.ts +0 -74
  24. package/lib/JJDF.js +0 -98
  25. package/lib/JJDF.js.map +0 -1
  26. package/lib/JJE.d.ts +0 -299
  27. package/lib/JJE.js +0 -401
  28. package/lib/JJE.js.map +0 -1
  29. package/lib/JJET.d.ts +0 -79
  30. package/lib/JJET.js +0 -114
  31. package/lib/JJET.js.map +0 -1
  32. package/lib/JJEx.d.ts +0 -63
  33. package/lib/JJEx.js +0 -83
  34. package/lib/JJEx.js.map +0 -1
  35. package/lib/JJHE.d.ts +0 -109
  36. package/lib/JJHE.js +0 -136
  37. package/lib/JJHE.js.map +0 -1
  38. package/lib/JJN-wrap.d.ts +0 -1
  39. package/lib/JJN-wrap.js +0 -46
  40. package/lib/JJN-wrap.js.map +0 -1
  41. package/lib/JJN.d.ts +0 -126
  42. package/lib/JJN.js +0 -166
  43. package/lib/JJN.js.map +0 -1
  44. package/lib/JJNx.d.ts +0 -126
  45. package/lib/JJNx.js +0 -157
  46. package/lib/JJNx.js.map +0 -1
  47. package/lib/JJSE.d.ts +0 -170
  48. package/lib/JJSE.js +0 -217
  49. package/lib/JJSE.js.map +0 -1
  50. package/lib/JJSR.d.ts +0 -71
  51. package/lib/JJSR.js +0 -90
  52. package/lib/JJSR.js.map +0 -1
  53. package/lib/JJT.d.ts +0 -92
  54. package/lib/JJT.js +0 -116
  55. package/lib/JJT.js.map +0 -1
  56. package/lib/case.d.ts +0 -60
  57. package/lib/case.js +0 -92
  58. package/lib/case.js.map +0 -1
  59. package/lib/components.d.ts +0 -147
  60. package/lib/components.js +0 -287
  61. package/lib/components.js.map +0 -1
  62. package/lib/helpers.d.ts +0 -159
  63. package/lib/helpers.js +0 -233
  64. package/lib/helpers.js.map +0 -1
  65. package/lib/index.d.ts +0 -33
  66. package/lib/index.js +0 -35
  67. package/lib/index.js.map +0 -1
  68. package/lib/internal.d.ts +0 -30
  69. package/lib/internal.js +0 -35
  70. package/lib/internal.js.map +0 -1
  71. package/lib/types.d.ts +0 -65
  72. package/lib/types.js +0 -2
  73. package/lib/types.js.map +0 -1
  74. package/lib/util.d.ts +0 -68
  75. package/lib/util.js +0 -90
  76. package/lib/util.js.map +0 -1
  77. 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.fromTag('div')
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/