jj 3.0.0-rc.6 → 3.0.0-rc.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jj",
3
- "version": "3.0.0-rc.6",
3
+ "version": "3.0.0-rc.7",
4
4
  "description": "A minimal DOM manipulation library with web components",
5
5
  "keywords": [
6
6
  "javascript",
package/skills/SKILL.md CHANGED
@@ -16,6 +16,7 @@ When converting native DOM code, framework code, or vague UI requests into JJ, d
16
16
  - Use `JJHE.tree` with a local `h` alias for multi-node or nested UI.
17
17
  - Use `setChild()`/`setChildren()` to replace content and `addChildMap()`/`setChildMap()` for array rendering.
18
18
  - Query with `find()`/`findAll()`/`closest()` instead of native `querySelector*` when JJ already covers the case.
19
+ - For form-like value elements (`input`, `select`, `textarea`, `progress`, etc.), prefer `getValue()` / `setValue(...)` over `.ref.value`.
19
20
  - Use `setText()` for user content and treat `setHTML(..., true)` as a trusted-content escape hatch.
20
21
  - For repeated child interactions, prefer one delegated listener on a stable parent over one listener per child.
21
22
  - Choose shadow DOM for self-contained widgets and light DOM for page-level content that should inherit global styles.
@@ -64,12 +65,12 @@ Each JJ wrapper exposes the native node via `.ref`.
64
65
 
65
66
  ```typescript
66
67
  // ✅ CORRECT — factory methods infer the precise generic type
67
- const div = JJHE.create('div') // JJHE<HTMLDivElement>
68
- const input = JJHE.create('input') // JJHE<HTMLInputElement>
69
- const svg = JJSE.create('svg') // JJSE<SVGSVGElement>
70
- const math = JJME.create('math') // JJME<MathMLElement>
71
- const frag = JJDF.create() // JJDF
72
- const btn = JJHE.fromId('my-btn') // JJHE<HTMLButtonElement>
68
+ const jjDiv = JJHE.create('div') // JJHE<HTMLDivElement>
69
+ const jjInput = JJHE.create('input') // JJHE<HTMLInputElement>
70
+ const jjSvg = JJSE.create('svg') // JJSE<SVGSVGElement>
71
+ const jjMath = JJME.create('math') // JJME<MathMLElement>
72
+ const jjFrag = JJDF.create() // JJDF
73
+ const jjBtn = JJHE.fromId('my-btn') // JJHE<HTMLButtonElement>
73
74
 
74
75
  // ❌ WRONG
75
76
  JJHE.create('svg') // throws — use JJSE.create('svg')
@@ -81,7 +82,7 @@ new JJHE(element) // don't call constructors directly
81
82
  All mutating methods return `this`. Chain as much as possible; access `.ref` only when a wrapper method does not exist.
82
83
 
83
84
  ```typescript
84
- const btn = JJHE.create('button')
85
+ const jjBtn = JJHE.create('button')
85
86
  .addClass('btn', 'primary')
86
87
  .setText('Save')
87
88
  .setAttr('type', 'submit')
@@ -102,7 +103,7 @@ latestChatResponse.addChild(
102
103
  )
103
104
 
104
105
  // ✅ also fine for flat mapped children
105
- const list = JJHE.create('ul').addChildMap(fruits, (fruit) => h('li', null, fruit))
106
+ const jjList = JJHE.create('ul').addChildMap(fruits, (fruit) => h('li', null, fruit))
106
107
 
107
108
  // ❌ avoid native-style wrapper escape hatches when JJ already covers it
108
109
  latestChatResponse.ref.appendChild(JJHE.create('h2').setText('User').ref)
@@ -125,13 +126,13 @@ Default heuristics from the tutorial:
125
126
  Wrap `document` with `JJD.from(document)` before querying.
126
127
 
127
128
  ```typescript
128
- const doc = JJD.from(document)
129
- const app = doc.find('#app', true) // throws when absent
130
- const card = doc.find('.card') // null when absent
131
- const items = doc.findAll('.item') // always an array
129
+ const jjDoc = JJD.from(document)
130
+ const jjApp = jjDoc.find('#app', true) // throws when absent
131
+ const jjCard = jjDoc.find('.card') // null when absent
132
+ const jjItems = jjDoc.findAll('.item') // always an array
132
133
 
133
134
  // Inside a custom element's shadow root
134
- const btn = this.getShadow(true).find('#submit')
135
+ const jjBtn = this.getShadow(true).find('#submit')
135
136
  ```
136
137
 
137
138
  Querying defaults from the tutorial:
@@ -148,80 +149,94 @@ Querying defaults from the tutorial:
148
149
 
149
150
  ```typescript
150
151
  // Attribute — singular
151
- el.setAttr('role', 'button')
152
- el.getAttr('role')
153
- el.rmAttr('hidden')
154
- el.swAttr('disabled', !isReady) // sets disabled="" or removes it
152
+ jjEl.setAttr('role', 'button')
153
+ jjEl.getAttr('role')
154
+ jjEl.rmAttr('hidden')
155
+ jjEl.swAttr('readonly') // auto: flips current state of the "readonly" attribute
156
+ jjEl.swAttr('disabled', !isReady) // sets disabled="" or removes it
155
157
 
156
158
  // Attribute — batch (null/undefined skipped)
157
- el.setAttrs({ type: 'text', placeholder: 'Search…' })
159
+ jjEl.setAttrs({ type: 'text', placeholder: 'Search…' })
158
160
 
159
161
  // Classes
160
- el.addClass('active')
161
- el.addClasses(['chip', 'selected'])
162
- el.rmClass('disabled')
163
- el.rmClasses(['pending', 'loading'])
164
- el.swClass('expanded', isExpanded) // explicit: adds when truthy, removes when falsy
165
- el.swClass('is-active') // auto: flips current state (adds if absent, removes if present)
166
- el.swAttr('disabled', !isReady) // explicit: sets disabled="" or removes it
167
- el.swAttr('readonly') // auto: flips current state
168
- el.setClasses({ active: isActive, disabled: !isReady })
169
- el.setClass('card card--featured') // replaces entire className
162
+ jjEl.addClass('active')
163
+ // Multiple classes via varargs
164
+ jjEl.addClass('active', 'selected')
165
+ jjEl.rmClass('active', 'loading')
166
+ // Multiple classes via array
167
+ jjEl.addClasses(['chip', 'selected'])
168
+ jjEl.rmClasses(['pending', 'loading'])
169
+ // Explicit mode: truthy adds, falsy removes
170
+ jjEl.swClass('expanded', isExpanded)
171
+ // Auto mode: flips current state
172
+ jjEl.swClass('is-active')
173
+
174
+ // Batch conditional class updates
175
+ jjEl.setClasses({ active: isActive, disabled: !isReady })
176
+ // Replace the entire className
177
+ jjEl.setClass('card card--featured')
170
178
 
171
179
  // Dataset
172
- el.getDataAttr('userId')
173
- el.hasDataAttr('userId')
174
- el.setDataAttr('userId', '42')
175
- el.setDataAttrs({ role: 'admin', team: 'ui' })
176
- el.rmDataAttr('userId')
177
- el.rmDataAttrs(['role', 'team'])
180
+ jjEl.getDataAttr('userId')
181
+ jjEl.hasDataAttr('userId')
182
+ jjEl.setDataAttr('userId', '42')
183
+ jjEl.setDataAttrs({ role: 'admin', team: 'ui' }) // batch set
184
+ jjEl.rmDataAttr('userId')
185
+ jjEl.rmDataAttr('role', 'team') // batch remove, varargs syntax
186
+ jjEl.rmDataAttrs(['role', 'team']) // batch remove, array syntax
178
187
 
179
188
  // ARIA
180
- el.getAriaAttr('hidden')
181
- el.hasAriaAttr('hidden')
182
- el.setAriaAttr('hidden', 'true')
183
- el.setAriaAttrs({ label: 'Dialog', modal: 'true' })
184
- el.rmAriaAttr('hidden')
189
+ jjEl.getAriaAttr('hidden')
190
+ jjEl.hasAriaAttr('hidden')
191
+ jjEl.setAriaAttr('hidden', 'true')
192
+ jjEl.setAriaAttrs({ label: 'Dialog', modal: 'true' })
193
+ jjEl.rmAriaAttr('hidden')
185
194
 
186
195
  // ARIA is not presence-based like HTML boolean attributes
187
196
  // Use explicit string states instead of swAttr()
188
- el.setAriaAttr('disabled', 'true')
197
+ jjEl.setAriaAttr('disabled', 'true')
189
198
 
190
199
  // Inline styles
191
- el.setStyle('color', 'var(--color-brand)')
192
- el.setStyles({ color: 'red', padding: '8px', border: null })
193
- el.rmStyle('color', 'padding')
200
+ jjEl.setStyle('color', 'var(--color-brand)')
201
+ jjEl.setStyles({ color: 'red', padding: '8px', border: null })
202
+ jjEl.rmStyle('color', 'padding')
203
+
204
+ // Value helpers (prefer over .ref.value)
205
+ jjEl.getValue()
206
+ jjEl.setValue('next')
194
207
  ```
195
208
 
209
+ Use `.ref.value` only when a JJ value helper is unavailable for your exact use case.
210
+
196
211
  ## Security — HTML Writes
197
212
 
198
213
  Prefer `.setText()` for any user-supplied content. `.setHTML()` requires an explicit `true` flag when the string is non-empty.
199
214
 
200
215
  ```typescript
201
- el.setText(userInput) // ✅ always safe
202
- el.setHTML('<p>Trusted markup</p>', true) // ✅ explicit opt-in
203
- el.setHTML('') // ✅ clearing is allowed without flag
204
- el.setHTML('<p>content</p>') // ❌ THROWS — missing unsafe flag
205
- el.ref.innerHTML = '…' // ❌ bypasses guard — avoid
216
+ jjEl.setText(userInput) // ✅ always safe
217
+ jjEl.setHTML('<p>Trusted markup</p>', true) // ✅ explicit opt-in
218
+ jjEl.setHTML('') // ✅ clearing is allowed without flag
219
+ jjEl.setHTML('<p>content</p>') // ❌ THROWS — missing unsafe flag
220
+ jjEl.ref.innerHTML = '…' // ❌ bypasses guard — avoid
206
221
  ```
207
222
 
208
223
  ## Events
209
224
 
210
225
  ```typescript
211
226
  // Native events
212
- el.on('click', handler)
213
- el.off('click', handler)
214
- el.trigger('click')
227
+ jjEl.on('click', handler)
228
+ jjEl.off('click', handler)
229
+ jjEl.triggerEvent('click')
215
230
 
216
231
  // Explicit event objects (equivalent to JJ helpers below)
217
- el.trigger(new Event('click', { bubbles: true, composed: true }))
218
- el.trigger(new CustomEvent('todo-toggle', { detail: { id: 1, done: true }, bubbles: true, composed: true }))
232
+ jjEl.trigger(new Event('click', { bubbles: true, composed: true }))
233
+ jjEl.trigger(new CustomEvent('todo-toggle', { detail: { id: 1, done: true }, bubbles: true, composed: true }))
219
234
 
220
235
  // Custom events — JJ defaults: bubbles: true, composed: true
221
236
  this.dispatchEvent(new CustomEvent('todo-toggle', { detail: { id: 1, done: true }, bubbles: true, composed: true }))
222
237
 
223
238
  // Fluent dispatch (same defaults)
224
- el.triggerEvent('click') // equivalent to trigger(new Event('click', { bubbles: true, composed: true }))
239
+ jjEl.triggerEvent('click') // equivalent to trigger(new Event('click', { bubbles: true, composed: true }))
225
240
  JJHE.from(this).triggerCustomEvent('todo-toggle', { id: 1, done: true })
226
241
  // equivalent to trigger(new CustomEvent('todo-toggle', { detail: { id: 1, done: true }, bubbles: true, composed: true }))
227
242
 
@@ -246,10 +261,10 @@ Guide defaults for event-heavy UI:
246
261
 
247
262
  ```typescript
248
263
  list.on('click', function (event) {
249
- const item = JJHE.from(event.target as Node).closest('[data-item-id]')
250
- if (!item) return
264
+ const jjItem = JJHE.from(event.target as Node).closest('[data-item-id]')
265
+ if (!jjItem) return
251
266
  this.addClass('handled')
252
- item.addClass('active')
267
+ jjItem.addClass('active')
253
268
  })
254
269
  ```
255
270
 
@@ -276,12 +291,12 @@ export class MyCard extends HTMLElement {
276
291
 
277
292
  #userName = ''
278
293
  #count = 0
279
- #root = null // JJSR wrapper; attached in constructor
294
+ #jjShadow = null // JJSR wrapper; attached in constructor
280
295
  #isInitialized = false
281
296
 
282
297
  constructor() {
283
298
  super()
284
- this.#root = JJHE.from(this).setShadow('open').getShadow(true)
299
+ this.#jjShadow = JJHE.from(this).setShadow('open').getShadow(true)
285
300
  }
286
301
 
287
302
  attributeChangedCallback(name, oldValue, newValue) {
@@ -307,16 +322,16 @@ export class MyCard extends HTMLElement {
307
322
 
308
323
  async connectedCallback() {
309
324
  if (!this.#isInitialized) {
310
- this.#root.init(await templatePromise, await stylePromise)
325
+ this.#jjShadow.init(await templatePromise, await stylePromise)
311
326
  this.#isInitialized = true
312
327
  }
313
328
  this.#render()
314
329
  }
315
330
 
316
331
  #render() {
317
- if (!this.#root) return // guard for attribute changes before mount
318
- this.#root.find('[data-role="name"]')?.setText(this.#userName)
319
- this.#root.find('[data-role="count"]')?.setText(String(this.#count))
332
+ if (!this.#jjShadow) return // guard for attribute changes before mount
333
+ this.#jjShadow.find('[data-role="name"]')?.setText(this.#userName)
334
+ this.#jjShadow.find('[data-role="count"]')?.setText(String(this.#count))
320
335
  }
321
336
  }
322
337
 
@@ -332,7 +347,7 @@ Template defaults from the tutorial:
332
347
  - Prefer `<template>` elements for reusable DOM snippets already present in the page.
333
348
  - Prefer `JJHE.tree()` or `JJHE.create()` when you need live wrapper references for later updates.
334
349
  - Keep template promises at module scope; for lazy loading, initialize them inside `connectedCallback()` with an `if (!templatePromise)` guard.
335
- - Use one stable `#root` wrapper per component: `JJHE.from(this)` for light DOM or `JJHE.from(this).setShadow(...).getShadow(true)` for shadow DOM.
350
+ - Use one stable wrapper per component: `#jjHost` with `JJHE.from(this)` for light DOM, or `#jjShadow` with `JJHE.from(this).setShadow(...).getShadow(true)` for shadow DOM.
336
351
  - Initialize template content once, then update specific nodes with `find(...).setText(...)` or other targeted wrapper operations.
337
352
 
338
353
  Guide defaults for attributes and queries:
@@ -373,18 +388,18 @@ const card = h(
373
388
 
374
389
  ```typescript
375
390
  // Clear children — internally uses replaceChildren()
376
- el.empty()
391
+ jjEl.empty()
377
392
 
378
393
  // Replace all children in one call (prefer over .empty().addChild())
379
- el.setChild(newChild)
380
- el.setChildren([childA, childB])
381
- el.setChildMap(items, (item) => JJHE.tree('li', null, item.label))
382
- el.setTemplate(templateElement)
394
+ jjEl.setChild(newChild)
395
+ jjEl.setChildren([childA, childB])
396
+ jjEl.setChildMap(items, (item) => JJHE.tree('li', null, item.label))
397
+ jjEl.setTemplate(templateElement)
383
398
 
384
399
  // Append
385
- el.addChild(child)
386
- el.addChildMap(items, (item) => JJHE.tree('li', null, item.label))
387
- el.addTemplate(await templatePromise) // clones before appending
400
+ jjEl.addChild(child)
401
+ jjEl.addChildMap(items, (item) => JJHE.tree('li', null, item.label))
402
+ jjEl.addTemplate(await templatePromise) // clones before appending
388
403
  ```
389
404
 
390
405
  `addChild` / `preChild` / `setChild` and map variants ignore `null`/`undefined`; all other non-node values are coerced to Text nodes.
@@ -399,10 +414,10 @@ Guide defaults for template and fragment usage:
399
414
  ## Node Traversal
400
415
 
401
416
  ```typescript
402
- const parent = el.getParent() // wrapped parent or null (detached)
403
- const children = el.getChildren() // wrapped child array (always an array)
404
- el.rm() // detach from parent (no-op if already detached)
405
- const ancestor = el.closest('[data-section]') // null if not found
417
+ const parent = jjEl.getParent() // wrapped parent or null (detached)
418
+ const children = jjEl.getChildren() // wrapped child array (always an array)
419
+ jjEl.rm() // detach from parent (no-op if already detached)
420
+ const ancestor = jjEl.closest('[data-section]') // null if not found
406
421
  ```
407
422
 
408
423
  ## Resource Loaders
@@ -450,10 +465,11 @@ Use higher-level public APIs like `attr2prop` and `defineComponent` instead of i
450
465
 
451
466
  1. **`.ts` extension in imports** — TypeScript source must use `.js` (`import { X } from './X.js'`).
452
467
  2. **`JJHE.create('svg')`** — throws; use `JJSE.create('svg')`.
453
- 3. **`el.setHTML(html)` without `true`** — throws when html is non-empty.
468
+ 3. **`jjEl.setHTML(html)` without `true`** — throws when html is non-empty.
454
469
  4. **Fetching template/style inside `connectedCallback`** — fetch at module scope so the network request is shared.
455
470
  5. **Not awaiting `Element.defined`** — markup may be parsed before the element is defined, causing flaky upgrades.
456
471
  6. **Breaking the chain with `.ref` unnecessarily** — use wrapper methods first; reach for `.ref` only when no wrapper method exists.
472
+ 7. **Using `.ref.value` for common value updates** — prefer `getValue()` / `setValue(...)` for wrapper-level reads/writes.
457
473
 
458
474
  ## Pitfall Prevention Rules (Component Work)
459
475
 
@@ -62,15 +62,16 @@ button {
62
62
  }
63
63
  ```
64
64
 
65
- ## Loading stylesheets
65
+ ## Loading css files
66
66
 
67
67
  ```js
68
- import { JJHE, fetchStyle } from 'jj'
68
+ import { JJD, JJHE, fetchStyle } from 'jj'
69
69
 
70
70
  const h = JJHE.tree
71
+ const jjDoc = JJD.from(document)
71
72
 
72
73
  // Hint browser to start loading early
73
- document.head.addChild(
74
+ jjDoc.find('head', true).addChild(
74
75
  h('link', {
75
76
  href: import.meta.resolve('./theme.css'),
76
77
  rel: 'preload',
@@ -3,15 +3,15 @@
3
3
  ## Native event listeners
4
4
 
5
5
  ```js
6
- el.on('click', handler) // addEventListener
7
- el.off('click', handler) // removeEventListener (same function reference)
8
- el.trigger('click') // dispatchEvent(new Event('click'))
6
+ jjEl.on('click', handler) // addEventListener
7
+ jjEl.off('click', handler) // removeEventListener (same function reference)
8
+ jjEl.triggerEvent('click') // creates and dispatches Event('click')
9
9
  ```
10
10
 
11
11
  The handler `this` inside `.on()` is bound to the JJ wrapper instance. Use `this.ref` to access the native element:
12
12
 
13
13
  ```js
14
- btn.on('click', function () {
14
+ jjBtn.on('click', function () {
15
15
  // this → the JJHE wrapper
16
16
  // this.ref → the HTMLButtonElement
17
17
  console.log(this.ref.textContent)
@@ -54,10 +54,16 @@ new CustomEvent('internal-ready', { bubbles: false, composed: false })
54
54
  ## Event delegation
55
55
 
56
56
  ```js
57
- const list = doc.find('#list', true)
58
- list.on('click', (e) => {
59
- const item = e.target.closest('[data-id]')
60
- if (item) handleClick(item.dataset.id)
57
+ import { JJD } from 'jj'
58
+
59
+ const jjDoc = JJD.from(document)
60
+ const jjList = jjDoc.find('#list', true)
61
+ jjList.on('click', (e) => {
62
+ if (!(e.target instanceof Element)) {
63
+ return
64
+ }
65
+ const itemEl = e.target.closest('[data-id]')
66
+ if (itemEl) handleClick(itemEl.dataset.id)
61
67
  })
62
68
  ```
63
69
 
@@ -65,7 +71,7 @@ list.on('click', (e) => {
65
71
 
66
72
  ```js
67
73
  const ctrl = new AbortController()
68
- el.ref.addEventListener('click', handler, { signal: ctrl.signal })
74
+ jjEl.ref.addEventListener('click', handler, { signal: ctrl.signal })
69
75
  // later:
70
76
  ctrl.abort() // removes listener
71
77
  ```
@@ -7,8 +7,8 @@ jQuery provides chainable selection and DOM operations. JJ is similar in API fee
7
7
  | jQuery | JJ equivalent |
8
8
  | ------------------------------ | --------------------------------------------------------------------------------- |
9
9
  | `$('#id')` | `JJD.from(document).find('#id')` |
10
- | `$('.cls')` (first match) | `doc.find('.cls')` |
11
- | `$('.cls')` (all matches) | `doc.findAll('.cls')` |
10
+ | `$('.cls')` (first match) | `jjDoc.find('.cls')` |
11
+ | `$('.cls')` (all matches) | `jjDoc.findAll('.cls')` |
12
12
  | `.addClass()` / `.removeClass` | `.addClass()` / `.rmClass()` |
13
13
  | `.swClass(cls, bool?)` | `.swClass(cls, force?)` — explicit when force is provided, auto-flip when omitted |
14
14
  | `.attr(name, val)` (write) | `.setAttr(name, val)` |
@@ -36,8 +36,8 @@ $('.card').addClass('active').css('color', 'red')
36
36
  JJ:
37
37
 
38
38
  ```js
39
- const doc = JJD.from(document)
40
- doc.findAll('.card').forEach((card) => card.addClass('active').setStyle('color', 'red'))
39
+ const jjDoc = JJD.from(document)
40
+ jjDoc.findAll('.card').forEach((jjCard) => jjCard.addClass('active').setStyle('color', 'red'))
41
41
  ```
42
42
 
43
43
  ## HTML write safety
@@ -51,8 +51,8 @@ $('#msg').html(userInput) // XSS risk
51
51
  JJ requires explicit acknowledgement:
52
52
 
53
53
  ```js
54
- doc.find('#msg').setHTML(trustedMarkup, true) // must pass true
55
- doc.find('#msg').setText(userInput) // safe for user content
54
+ jjDoc.find('#msg').setHTML(trustedMarkup, true) // must pass true
55
+ jjDoc.find('#msg').setText(userInput) // safe for user content
56
56
  ```
57
57
 
58
58
  ## Event delegation
@@ -66,7 +66,10 @@ $(document).on('click', '.item', handler)
66
66
  JJ — use native event delegation via `matches`:
67
67
 
68
68
  ```js
69
- doc.on('click', (e) => {
69
+ jjDoc.on('click', (e) => {
70
+ if (!(e.target instanceof Element)) {
71
+ return
72
+ }
70
73
  if (e.target.matches('.item')) handler(e)
71
74
  })
72
75
  ```
@@ -13,7 +13,7 @@ Lit adds reactive properties, template literals, and scoped CSS on top of native
13
13
  | `html\`<div>\`` | `JJHE.tree('div', …)` or `JJHE.create('div')` |
14
14
  | `@event="${handler}"` | `.on('event', handler)` in `connectedCallback` |
15
15
  | `updated()` hook | Call `#render()` from setters that change visible state |
16
- | `this.renderRoot.query…` | `this.#root.find(selector)` |
16
+ | `this.renderRoot.query…` | `this.#jjShadow.find(selector)` |
17
17
 
18
18
  ## Component example
19
19
 
@@ -46,12 +46,12 @@ export class MyCounter extends HTMLElement {
46
46
  static defined = defineComponent('my-counter', MyCounter)
47
47
 
48
48
  #count = 0
49
- #root = null
49
+ #jjShadow = null
50
50
  #isInitialized = false
51
51
 
52
52
  constructor() {
53
53
  super()
54
- this.#root = JJHE.from(this).setShadow('open').getShadow(true)
54
+ this.#jjShadow = JJHE.from(this).setShadow('open').getShadow(true)
55
55
  }
56
56
 
57
57
  attributeChangedCallback(name, oldValue, newValue) {
@@ -67,7 +67,7 @@ export class MyCounter extends HTMLElement {
67
67
 
68
68
  async connectedCallback() {
69
69
  if (!this.#isInitialized) {
70
- this.#root
70
+ this.#jjShadow
71
71
  .init('<button id="btn">0</button>', await stylePromise)
72
72
  .find('#btn', true)
73
73
  .on('click', () => {
@@ -79,7 +79,7 @@ export class MyCounter extends HTMLElement {
79
79
  }
80
80
 
81
81
  #render() {
82
- this.#root?.find('#btn')?.setText(String(this.#count))
82
+ this.#jjShadow?.find('#btn')?.setText(String(this.#count))
83
83
  }
84
84
  }
85
85
  ```
@@ -5,16 +5,16 @@
5
5
  Always start from a wrapped container — most commonly the document or a shadow root:
6
6
 
7
7
  ```js
8
- const doc = JJD.from(document)
9
- const shadow = this.#root // JJSR inside a custom element
8
+ const jjDoc = JJD.from(document)
9
+ const jjShadow = this.#jjShadow // JJSR inside a custom element
10
10
  ```
11
11
 
12
12
  ## find — first match
13
13
 
14
14
  ```js
15
- const card = doc.find('.card') // null when absent
16
- const app = doc.find('#app', true) // throws TypeError when absent
17
- const btn = shadow.find('#submit') // scoped to shadow root
15
+ const jjCard = jjDoc.find('.card') // null when absent
16
+ const jjApp = jjDoc.find('#app', true) // throws ReferenceError when absent
17
+ const jjBtn = jjShadow.find('#submit') // scoped to shadow root
18
18
  ```
19
19
 
20
20
  Pass `true` as the second argument when the element is required. This produces a clearer error than a null-access crash later.
@@ -22,9 +22,9 @@ Pass `true` as the second argument when the element is required. This produces a
22
22
  You can use it in combination with a more specific query to simultaneously assert your expectations. For example, if you want a reference to a button with the id `submit`, you could write:
23
23
 
24
24
  ```js
25
- const submitBtn = doc.find('#submit-btn', true)
25
+ const jjSubmitBtn = jjDoc.find('#submit-btn', true)
26
26
 
27
- if (!(submitBtn instanceof HTMLButtonElement)) {
27
+ if (!(jjSubmitBtn.ref instanceof HTMLButtonElement)) {
28
28
  throw new Error('Expected #submit-btn to be an HTMLButtonElement.')
29
29
  }
30
30
  ```
@@ -34,7 +34,7 @@ But a shorter and more expressive way to write this is:
34
34
  ```js
35
35
  // This will throw an exception if an element with id is not found
36
36
  // OR if it's found but not a button
37
- const submitBtn = doc.find('button#submit-btn', true)
37
+ const jjSubmitBtn = jjDoc.find('button#submit-btn', true)
38
38
  ```
39
39
 
40
40
  This helps catch errors early and narrow down troubleshooting.
@@ -43,19 +43,19 @@ When `find` returns a wrapper, keep the wrapper unless you need a native API tha
43
43
 
44
44
  ```js
45
45
  // ✅ keep wrapper value
46
- const jjSubmitBtn = doc.find('button#submit-btn', true)
46
+ const jjSubmitBtn = jjDoc.find('button#submit-btn', true)
47
47
  jjSubmitBtn.on('click', onSubmit)
48
48
 
49
49
  // ❌ avoid unwrap + re-wrap noise
50
- const submitRef = doc.find('button#submit-btn', true).ref
50
+ const submitRef = jjDoc.find('button#submit-btn', true).ref
51
51
  const jjSubmitBtnAgain = JJHE.from(submitRef)
52
52
  ```
53
53
 
54
54
  ## findAll — all matches
55
55
 
56
56
  ```js
57
- const items = doc.findAll('li.item') // always an array (may be empty)
58
- items.forEach((item) => item.addClass('loaded'))
57
+ const jjItems = jjDoc.findAll('li.item') // always an array (may be empty)
58
+ jjItems.forEach((jjItem) => jjItem.addClass('loaded'))
59
59
  ```
60
60
 
61
61
  ## closest — ancestor lookup
@@ -63,16 +63,19 @@ items.forEach((item) => item.addClass('loaded'))
63
63
  Use `.closest()` on element wrappers for event delegation or tree navigation:
64
64
 
65
65
  ```js
66
- doc.on('click', (e) => {
67
- const item = JJHE.from(e.target).closest('[data-item-id]')
68
- if (item) handleItemClick(item.getAttr('data-item-id'))
66
+ jjDoc.on('click', (e) => {
67
+ if (!(e.target instanceof Node)) {
68
+ return
69
+ }
70
+ const jjItem = JJHE.from(e.target).closest('[data-item-id]')
71
+ if (jjItem) handleItemClick(jjItem.getAttr('data-item-id'))
69
72
  })
70
73
  ```
71
74
 
72
75
  ## fromId — direct ID lookup
73
76
 
74
77
  ```js
75
- const btn = JJHE.fromId('submit-btn') // typed as JJHE<HTMLButtonElement>
78
+ const jjBtn = JJHE.fromId('submit-btn') // typed as JJHE<HTMLButtonElement>
76
79
  ```
77
80
 
78
81
  ## When to use .ref for queries
@@ -31,12 +31,12 @@ JJ:
31
31
  import { JJHE } from 'jj'
32
32
 
33
33
  let count = 0
34
- const btn = JJHE.create('button').setText('0')
35
- btn.on('click', () => {
34
+ const jjBtn = JJHE.create('button').setText('0')
35
+ jjBtn.on('click', () => {
36
36
  count++
37
- btn.setText(String(count))
37
+ jjBtn.setText(String(count))
38
38
  })
39
- document.body.appendChild(btn.ref)
39
+ document.body.appendChild(jjBtn.ref)
40
40
  ```
41
41
 
42
42
  ## Component props → observed attributes
@@ -73,7 +73,7 @@ JJ — child dispatches, parent listens:
73
73
  JJHE.from(this).triggerCustomEvent('todo-toggle', { id: this.#id })
74
74
 
75
75
  // parent
76
- container.on('todo-toggle', (e) => handleToggle(e.detail.id))
76
+ jjContainer.on('todo-toggle', (e) => handleToggle(e.detail.id))
77
77
  ```
78
78
 
79
79
  ## Browser references
@@ -32,9 +32,9 @@ JJ:
32
32
  import { JJHE } from 'jj'
33
33
 
34
34
  let count = 0
35
- const btn = JJHE.create('button').setText('0')
36
- btn.on('click', () => btn.setText(String(++count)))
37
- document.body.appendChild(btn.ref)
35
+ const jjBtn = JJHE.create('button').setText('0')
36
+ jjBtn.on('click', () => jjBtn.setText(String(++count)))
37
+ document.body.appendChild(jjBtn.ref)
38
38
  ```
39
39
 
40
40
  ## Outbound events
@@ -33,9 +33,9 @@ import { JJHE } from '../src/index.js'
33
33
  describe('JJHE', () => {
34
34
  describe('static create()', () => {
35
35
  it('creates element from tag name', () => {
36
- const div = JJHE.create('div')
37
- assert.ok(div instanceof JJHE)
38
- assert.strictEqual(div.ref.tagName, 'DIV')
36
+ const jjDiv = JJHE.create('div')
37
+ assert.ok(jjDiv instanceof JJHE)
38
+ assert.strictEqual(jjDiv.ref.tagName, 'DIV')
39
39
  })
40
40
 
41
41
  it('throws TypeError for non-string tagName', () => {
@@ -68,12 +68,12 @@ it('renders title in shadow', async () => {
68
68
 
69
69
  ```js
70
70
  it('dispatches todo-toggle with detail', () => {
71
- const el = JJHE.create('div')
71
+ const jjEl = JJHE.create('div')
72
72
  let captured = null
73
- el.on('todo-toggle', (e) => {
73
+ jjEl.on('todo-toggle', (e) => {
74
74
  captured = e.detail
75
75
  })
76
- el.triggerCustomEvent('todo-toggle', { id: 1, done: true })
76
+ jjEl.triggerCustomEvent('todo-toggle', { id: 1, done: true })
77
77
  assert.deepStrictEqual(captured, { id: 1, done: true })
78
78
  })
79
79
  ```
@@ -30,12 +30,12 @@ JJ:
30
30
  import { JJHE } from 'jj'
31
31
 
32
32
  let message = ''
33
- const p = JJHE.create('p').setText('')
34
- const input = JJHE.create('input')
33
+ const jjMessage = JJHE.create('p').setText('')
34
+ const jjInput = JJHE.create('input')
35
35
  .setAttr('type', 'text')
36
- .on('input', (e) => {
37
- message = e.target.value
38
- p.setText(message)
36
+ .on('input', () => {
37
+ message = jjInput.getValue()
38
+ jjMessage.setText(message)
39
39
  })
40
40
  ```
41
41
 
@@ -62,7 +62,7 @@ set count(v) {
62
62
  ## v-for replacement
63
63
 
64
64
  ```js
65
- list.addChildMap(items, (item) => JJHE.tree('li', { class: 'item' }, item.label))
65
+ jjList.addChildMap(items, (item) => JJHE.tree('li', { class: 'item' }, item.label))
66
66
  ```
67
67
 
68
68
  ## Browser references
@@ -37,10 +37,16 @@ use `defineComponent()` which also calls `customElements.whenDefined()`.
37
37
  const templatePromise = fetchTemplate(import.meta.resolve('./my-component.html'))
38
38
  const stylePromise = fetchStyle(import.meta.resolve('./my-component.css'))
39
39
 
40
+ constructor() {
41
+ super()
42
+ this.#jjHost = JJHE.from(this).setShadow('open')
43
+ this.#jjShadow = this.#jjHost.getShadow(true)
44
+ }
45
+
40
46
  async connectedCallback() {
41
- // setShadow(mode) in the constructor, then initShadow(template, ...styles) here
42
- JJHE.from(this).initShadow(await templatePromise, await stylePromise)
43
- this.#root.find('#btn').on('click', this.#handleClick)
47
+ // setShadow(mode) in the constructor, then initialize once here
48
+ this.#jjHost.initShadow(await templatePromise, await stylePromise)
49
+ this.#jjShadow.find('#btn', true).on('click', this.#handleClick)
44
50
  this.#render()
45
51
  }
46
52
  ```
@@ -63,8 +69,8 @@ attributeChangedCallback(name, oldValue, newValue) {
63
69
  // attr2prop fires attributeChangedCallback BEFORE connectedCallback on initial parse.
64
70
  // Guard renders until shadow is ready:
65
71
  #render() {
66
- if (!this.#root) return
67
- this.#root.find('[data-role="name"]')?.setText(this.#userName)
72
+ if (!this.#jjShadow) return
73
+ this.#jjShadow.find('[data-role="name"]')?.setText(this.#userName)
68
74
  }
69
75
  ```
70
76
 
@@ -74,8 +80,8 @@ Skip `setShadow` and update children directly when page-level styling should app
74
80
 
75
81
  ```js
76
82
  async connectedCallback() {
77
- this.#root = JJHE.from(this)
78
- this.#root.setTemplate(await templatePromise)
83
+ this.#jjHost = JJHE.from(this)
84
+ this.#jjHost.setTemplate(await templatePromise)
79
85
  this.#render()
80
86
  }
81
87
  ```