jj 2.5.0 → 2.7.2

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