jj 3.0.0-rc.4 → 3.0.0-rc.6

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.4",
3
+ "version": "3.0.0-rc.6",
4
4
  "description": "A minimal DOM manipulation library with web components",
5
5
  "keywords": [
6
6
  "javascript",
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "files": [
30
30
  "lib",
31
- "SKILL.md"
31
+ "skills"
32
32
  ],
33
33
  "scripts": {
34
34
  "fmt": "prettier --write .",
@@ -0,0 +1,487 @@
1
+ ---
2
+ name: jj
3
+ description: Expert guide for the JJ DOM manipulation library. Load this skill whenever you need to write, debug, or review JJ code; create native web components using JJ's defineComponent, setShadow, fetchTemplate, or fetchStyle; translate React, Vue, Svelte, Angular, jQuery, or Lit patterns to JJ idioms; work with JJHE, JJD, JJSE, JJME, JJDF, JJSR, JJET, JJN, or JJT wrappers; or write JJ tests with jsdom. If any JJ class name or helper function appears in the conversation, always load this skill.
4
+ ---
5
+
6
+ # JJ DOM Library
7
+
8
+ JJ is a minimal, zero-dependency TypeScript library that wraps browser DOM interfaces in fluent, type-safe classes. It complements native browser APIs rather than replacing them.
9
+
10
+ ## Translation Checklist
11
+
12
+ When converting native DOM code, framework code, or vague UI requests into JJ, default to this order of thought:
13
+
14
+ - Start from wrappers, not native nodes: `JJD.from(document)`, `JJHE.create()`, `JJHE.tree()`, `JJET.from(window)`, `getShadow(true)`.
15
+ - Keep values wrapped and chain operations; use `.ref` only for native APIs JJ does not provide.
16
+ - Use `JJHE.tree` with a local `h` alias for multi-node or nested UI.
17
+ - Use `setChild()`/`setChildren()` to replace content and `addChildMap()`/`setChildMap()` for array rendering.
18
+ - Query with `find()`/`findAll()`/`closest()` instead of native `querySelector*` when JJ already covers the case.
19
+ - Use `setText()` for user content and treat `setHTML(..., true)` as a trusted-content escape hatch.
20
+ - For repeated child interactions, prefer one delegated listener on a stable parent over one listener per child.
21
+ - Choose shadow DOM for self-contained widgets and light DOM for page-level content that should inherit global styles.
22
+ - For components, keep fetched templates/styles at module scope, attach shadow root in the constructor, initialize once, then update targeted nodes instead of rebuilding the whole tree.
23
+ - Prefer `'open'` shadow roots unless the user explicitly needs `'closed'`; open mode is easier to test and debug.
24
+ - Use plain JS state plus targeted wrapper updates by default; do not invent a virtual DOM style rerender loop unless the user explicitly wants one.
25
+
26
+ ## Naming Conventions
27
+
28
+ In this repository, prefix variables that hold JJ wrapper instances with `jj`.
29
+
30
+ ```typescript
31
+ const jjDoc = JJD.from(document)
32
+ const jjFruits = jjDoc.find('#fruits', true)
33
+ const jjSubmitBtn = jjDoc.find('button#submit', true)
34
+ const jjDialog = JJHE.create('dialog')
35
+ ```
36
+
37
+ Naming defaults:
38
+
39
+ - Use `jj*` for JJ wrappers, including private fields: `#jjHost` for `JJHE.from(this)` (the wrapped host element) and `#jjShadow` for `this.#jjHost.getShadow()` (the wrapped shadow root).
40
+ - Do not use `jj*` for plain data like `fruits`, `title`, `isOpen`, or `userName`.
41
+ - Do not use `jj*` for native DOM values; prefer names like `formEl`, `shadowRoot`, `inputRef`, or `styleSheet`.
42
+ - For promises, use normal names with `Promise`, like `templatePromise` or `stylePromise`.
43
+ - `h` is the main intentional exception: use it as the local alias for `JJHE.tree`.
44
+
45
+ ## Wrapper Hierarchy
46
+
47
+ Each JJ wrapper exposes the native node via `.ref`.
48
+
49
+ | Class | Wraps | Key additions |
50
+ | ----- | ---------------- | ------------------------------------------------------ |
51
+ | JJET | EventTarget | `.on()`, `.off()`, `.trigger()`, `.run()` |
52
+ | JJN | Node | `.getParent()`, `.getChildren()`, `.rm()`, `.clone()` |
53
+ | JJD | Document | `.find()`, `.findAll()` |
54
+ | JJDF | DocumentFragment | `.addTemplate()`, `.setTemplate()`, batch child ops |
55
+ | JJE | Element | Attributes, classes, ARIA, visibility, HTML write |
56
+ | JJHE | HTMLElement | `.setText()`, `.setStyle()`, `.setShadow()`, `.tree()` |
57
+ | JJSE | SVGElement | SVG namespace factory |
58
+ | JJME | MathMLElement | MathML namespace factory |
59
+ | JJSR | ShadowRoot | `.find()`, `.findAll()`, `.addStyle()`, `.init()` |
60
+ | JJDF | DocumentFragment | Fragment operations |
61
+ | JJT | Text | `.getText()`, `.setText()` |
62
+
63
+ ## Type-Safe Creation — Always Use Factory Methods
64
+
65
+ ```typescript
66
+ // ✅ 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>
73
+
74
+ // ❌ WRONG
75
+ JJHE.create('svg') // throws — use JJSE.create('svg')
76
+ new JJHE(element) // don't call constructors directly
77
+ ```
78
+
79
+ ## Chaining
80
+
81
+ All mutating methods return `this`. Chain as much as possible; access `.ref` only when a wrapper method does not exist.
82
+
83
+ ```typescript
84
+ const btn = JJHE.create('button')
85
+ .addClass('btn', 'primary')
86
+ .setText('Save')
87
+ .setAttr('type', 'submit')
88
+ .setAriaAttr('label', 'Save changes')
89
+ .on('click', handleSave)
90
+ ```
91
+
92
+ ## Tutorial Defaults — Prefer JJ Idioms Over Native DOM Steps
93
+
94
+ When translating browser DOM code into JJ, do not mechanically keep native patterns like repeated `appendChild`, `querySelector`, or unwrap/re-wrap flows. Prefer the JJ equivalent that keeps work inside wrappers.
95
+
96
+ ```typescript
97
+ // ✅ preferred: build a subtree once
98
+ const h = JJHE.tree
99
+
100
+ latestChatResponse.addChild(
101
+ h('section', null, h('h2', null, 'User'), h('div', null, userPrompt), h('h2', null, 'Assistant'), assistantMessage),
102
+ )
103
+
104
+ // ✅ also fine for flat mapped children
105
+ const list = JJHE.create('ul').addChildMap(fruits, (fruit) => h('li', null, fruit))
106
+
107
+ // ❌ avoid native-style wrapper escape hatches when JJ already covers it
108
+ latestChatResponse.ref.appendChild(JJHE.create('h2').setText('User').ref)
109
+ latestChatResponse.ref.appendChild(JJHE.create('div').setText(userPrompt).ref)
110
+ latestChatResponse.ref.appendChild(JJHE.create('h2').setText('Assistant').ref)
111
+ latestChatResponse.ref.appendChild(assistantMessage.ref)
112
+ ```
113
+
114
+ Default heuristics from the tutorial:
115
+
116
+ - Use `JJHE.tree` with a local `h` alias when creating multiple siblings or any nested subtree.
117
+ - Use `create()` for one-off elements; switch to `tree()` as soon as structure becomes non-trivial.
118
+ - Prefer `setChild()` or `setChildren()` when replacing content, not `.empty().addChild()`.
119
+ - Prefer `addChildMap()` or `setChildMap()` when rendering from arrays.
120
+ - Keep values wrapped. Reach for `.ref` only for native APIs JJ does not expose.
121
+ - Use JJ verb families consistently: `set*` replaces, `add*` appends, `pre*` prepends, `rm*` removes, `sw*` toggles.
122
+
123
+ ## Document Queries
124
+
125
+ Wrap `document` with `JJD.from(document)` before querying.
126
+
127
+ ```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
132
+
133
+ // Inside a custom element's shadow root
134
+ const btn = this.getShadow(true).find('#submit')
135
+ ```
136
+
137
+ Querying defaults from the tutorial:
138
+
139
+ - Start from a wrapped container like `JJD.from(document)` or a `JJSR` shadow root.
140
+ - Prefer `find(selector, true)` when absence is a bug; it fails earlier and more clearly than a later null access.
141
+ - Prefer narrower selectors that encode expectations, like `button#submit`, instead of broad lookups plus manual type checks.
142
+ - Use `findAll()` for arrays of wrappers and keep operating on wrappers instead of unwrapping to native elements.
143
+ - Do not use `.ref.querySelector(...)` or `.ref.querySelectorAll(...)` when `find()` or `findAll()` already covers the case.
144
+ - Use `.closest()` on wrappers for event delegation and ancestor lookup.
145
+ - Use `JJHE.fromId('submit-btn')` for direct ID lookup when you already know the target is an HTML element.
146
+
147
+ ## Attributes, Classes, Styles
148
+
149
+ ```typescript
150
+ // 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
155
+
156
+ // Attribute — batch (null/undefined skipped)
157
+ el.setAttrs({ type: 'text', placeholder: 'Search…' })
158
+
159
+ // 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
170
+
171
+ // 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'])
178
+
179
+ // 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')
185
+
186
+ // ARIA is not presence-based like HTML boolean attributes
187
+ // Use explicit string states instead of swAttr()
188
+ el.setAriaAttr('disabled', 'true')
189
+
190
+ // Inline styles
191
+ el.setStyle('color', 'var(--color-brand)')
192
+ el.setStyles({ color: 'red', padding: '8px', border: null })
193
+ el.rmStyle('color', 'padding')
194
+ ```
195
+
196
+ ## Security — HTML Writes
197
+
198
+ Prefer `.setText()` for any user-supplied content. `.setHTML()` requires an explicit `true` flag when the string is non-empty.
199
+
200
+ ```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
206
+ ```
207
+
208
+ ## Events
209
+
210
+ ```typescript
211
+ // Native events
212
+ el.on('click', handler)
213
+ el.off('click', handler)
214
+ el.trigger('click')
215
+
216
+ // 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 }))
219
+
220
+ // Custom events — JJ defaults: bubbles: true, composed: true
221
+ this.dispatchEvent(new CustomEvent('todo-toggle', { detail: { id: 1, done: true }, bubbles: true, composed: true }))
222
+
223
+ // Fluent dispatch (same defaults)
224
+ el.triggerEvent('click') // equivalent to trigger(new Event('click', { bubbles: true, composed: true }))
225
+ JJHE.from(this).triggerCustomEvent('todo-toggle', { id: 1, done: true })
226
+ // equivalent to trigger(new CustomEvent('todo-toggle', { detail: { id: 1, done: true }, bubbles: true, composed: true }))
227
+
228
+ // Override defaults for internal-only events
229
+ new CustomEvent('panel-ready', { bubbles: false, composed: false })
230
+ ```
231
+
232
+ Event defaults from the tutorial:
233
+
234
+ - Prefer `.on()` and `.off()` on wrappers over native `addEventListener`/`removeEventListener` when already working with JJ values.
235
+ - Prefer `.triggerEvent()` and `.triggerCustomEvent()` for common JJ event dispatch; they default to `bubbles: true` and `composed: true`.
236
+ - Use `triggerCustomEvent(name, detail)` for component-to-parent communication instead of ad hoc callback plumbing.
237
+ - Use `bubbles: false` and `composed: false` only for intentionally internal events.
238
+ - Keep event code close to the wrapper it affects so later DOM updates stay targeted and local.
239
+
240
+ Guide defaults for event-heavy UI:
241
+
242
+ - Prefer event delegation on a common parent for repeated child actions instead of binding one listener per item.
243
+ - Use `.closest()` to recover the intended delegated target from `event.target`.
244
+ - When you need JJ's wrapper-bound `this` inside a listener, use `function` syntax, not an arrow.
245
+ - Native UI events like `click`, `input`, and `change` already cross shadow boundaries; custom events do not unless `composed: true`.
246
+
247
+ ```typescript
248
+ list.on('click', function (event) {
249
+ const item = JJHE.from(event.target as Node).closest('[data-item-id]')
250
+ if (!item) return
251
+ this.addClass('handled')
252
+ item.addClass('active')
253
+ })
254
+ ```
255
+
256
+ ## Custom Elements — Complete Pattern
257
+
258
+ Fetch template and style at **module scope** — loaded once, shared across all instances.
259
+
260
+ Guide defaults for component shape:
261
+
262
+ - Use shadow DOM for self-contained widgets and design-system components; use light DOM for sections that should inherit page styling and normal document flow.
263
+ - Prefer `'open'` shadow mode unless stricter encapsulation is a hard requirement.
264
+ - `attributeChangedCallback()` can run before `connectedCallback()` for parsed attributes, so setters and render paths must tolerate pre-mount state.
265
+ - Use `disconnectedCallback()` only to clean up external side effects like document listeners, timers, observers, or subscriptions; do not tear down the shadow root just because the element was detached.
266
+
267
+ ```typescript
268
+ import { attr2prop, defineComponent, fetchStyle, fetchTemplate, JJHE } from 'jj'
269
+
270
+ const templatePromise = fetchTemplate(import.meta.resolve('./my-card.html'))
271
+ const stylePromise = fetchStyle(import.meta.resolve('./my-card.css'))
272
+
273
+ export class MyCard extends HTMLElement {
274
+ static observedAttributes = ['user-name', 'count']
275
+ static defined = defineComponent('my-card', MyCard)
276
+
277
+ #userName = ''
278
+ #count = 0
279
+ #root = null // JJSR wrapper; attached in constructor
280
+ #isInitialized = false
281
+
282
+ constructor() {
283
+ super()
284
+ this.#root = JJHE.from(this).setShadow('open').getShadow(true)
285
+ }
286
+
287
+ attributeChangedCallback(name, oldValue, newValue) {
288
+ // Converts kebab-case → camelCase, then calls the matching setter
289
+ attr2prop(this, name, oldValue, newValue)
290
+ }
291
+
292
+ get userName() {
293
+ return this.#userName
294
+ }
295
+ set userName(v) {
296
+ this.#userName = String(v ?? '')
297
+ this.#render()
298
+ }
299
+
300
+ get count() {
301
+ return this.#count
302
+ }
303
+ set count(v) {
304
+ this.#count = Number(v) || 0
305
+ this.#render()
306
+ }
307
+
308
+ async connectedCallback() {
309
+ if (!this.#isInitialized) {
310
+ this.#root.init(await templatePromise, await stylePromise)
311
+ this.#isInitialized = true
312
+ }
313
+ this.#render()
314
+ }
315
+
316
+ #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))
320
+ }
321
+ }
322
+
323
+ // Caller must await before using the custom element tag
324
+ await MyCard.defined
325
+ // Or multiple in parallel
326
+ await Promise.all([MyCard.defined, OtherCard.defined])
327
+ ```
328
+
329
+ Template defaults from the tutorial:
330
+
331
+ - Prefer fetched `.html` templates for large static markup.
332
+ - Prefer `<template>` elements for reusable DOM snippets already present in the page.
333
+ - Prefer `JJHE.tree()` or `JJHE.create()` when you need live wrapper references for later updates.
334
+ - 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.
336
+ - Initialize template content once, then update specific nodes with `find(...).setText(...)` or other targeted wrapper operations.
337
+
338
+ Guide defaults for attributes and queries:
339
+
340
+ - Always coerce attribute-backed values in setters because HTML attributes arrive as strings.
341
+ - Query inside shadow DOM from the `JJSR` wrapper, never from `document`.
342
+ - Use specific selectors like `button#submit` or `[data-role="title"]` so the selector carries intent.
343
+
344
+ State defaults from the tutorial:
345
+
346
+ - Prefer plain objects or classes for state and update the exact affected wrappers in event handlers or setters.
347
+ - Prefer targeted updates like `value.setText(String(state.count))` over rebuilding an entire subtree for a small change.
348
+ - Use getters/setters or small helper methods when they make state transitions clearer, not because JJ requires a framework-style abstraction.
349
+ - Reach for external state libraries only when the application actually needs cross-cutting coordination beyond local JS state.
350
+
351
+ `defineComponent()` returns `Promise<boolean>`:
352
+
353
+ - `false` — newly defined by this call
354
+ - `true` — already defined with the same constructor
355
+
356
+ ## Tree Builder
357
+
358
+ `JJHE.tree` is a factory for declarative element trees. Alias as `h` for brevity.
359
+
360
+ ```typescript
361
+ const h = JJHE.tree
362
+
363
+ const card = h(
364
+ 'article',
365
+ { class: 'card' },
366
+ h('h2', null, title),
367
+ h('p', { class: 'body' }, description),
368
+ h('footer', null, h('a', { href: url }, 'Read more')),
369
+ )
370
+ ```
371
+
372
+ ## Children and Templates
373
+
374
+ ```typescript
375
+ // Clear children — internally uses replaceChildren()
376
+ el.empty()
377
+
378
+ // 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)
383
+
384
+ // Append
385
+ el.addChild(child)
386
+ el.addChildMap(items, (item) => JJHE.tree('li', null, item.label))
387
+ el.addTemplate(await templatePromise) // clones before appending
388
+ ```
389
+
390
+ `addChild` / `preChild` / `setChild` and map variants ignore `null`/`undefined`; all other non-node values are coerced to Text nodes.
391
+
392
+ Guide defaults for template and fragment usage:
393
+
394
+ - `addTemplate()` and `setTemplate()` always clone the input before appending; reuse the same template value safely.
395
+ - Prefer `setTemplate()` over `empty().addTemplate()` when replacing all content.
396
+ - Prefer `addChildMap()` or `setChildMap()` over manually building a fragment when rendering arrays.
397
+ - Use `JJDF.create()` when you need to assemble multiple sibling nodes before one insertion.
398
+
399
+ ## Node Traversal
400
+
401
+ ```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
406
+ ```
407
+
408
+ ## Resource Loaders
409
+
410
+ ```typescript
411
+ import { JJHE, fetchStyle, fetchTemplate } from 'jj'
412
+
413
+ const h = JJHE.tree
414
+
415
+ // Hint browser to preload early with native <link>
416
+ document.head.append(
417
+ h('link', {
418
+ href: import.meta.resolve('./bundle.js'),
419
+ rel: 'modulepreload',
420
+ }).ref,
421
+ )
422
+ document.head.append(
423
+ h('link', {
424
+ href: import.meta.resolve('./main.css'),
425
+ rel: 'preload',
426
+ as: 'style',
427
+ }).ref,
428
+ )
429
+
430
+ // Load a CSSStyleSheet for adoptedStyleSheets or setShadow
431
+ const sheet = await fetchStyle(import.meta.resolve('./theme.css'))
432
+ document.adoptedStyleSheets = [sheet]
433
+
434
+ // Load a DocumentFragment for addTemplate / setShadow
435
+ const fragment = await fetchTemplate(import.meta.resolve('./dialog.html'))
436
+ ```
437
+
438
+ Guide defaults for browser-native loading hints:
439
+
440
+ - Use native `<link>` hints built with `JJHE.tree` and appended to `head` for `preload`, `prefetch`, and `modulepreload`.
441
+ - Keep the `as` value explicit for `preload` instead of inferring it from file extensions.
442
+ - Use `preload` for current-page needs, `prefetch` for probable future navigation, and `modulepreload` for module graphs you want fetched early.
443
+
444
+ ## String Casing
445
+
446
+ String case-conversion helpers are internal implementation details.
447
+ Use higher-level public APIs like `attr2prop` and `defineComponent` instead of importing low-level casing utilities.
448
+
449
+ ## Common mistakes
450
+
451
+ 1. **`.ts` extension in imports** — TypeScript source must use `.js` (`import { X } from './X.js'`).
452
+ 2. **`JJHE.create('svg')`** — throws; use `JJSE.create('svg')`.
453
+ 3. **`el.setHTML(html)` without `true`** — throws when html is non-empty.
454
+ 4. **Fetching template/style inside `connectedCallback`** — fetch at module scope so the network request is shared.
455
+ 5. **Not awaiting `Element.defined`** — markup may be parsed before the element is defined, causing flaky upgrades.
456
+ 6. **Breaking the chain with `.ref` unnecessarily** — use wrapper methods first; reach for `.ref` only when no wrapper method exists.
457
+
458
+ ## Pitfall Prevention Rules (Component Work)
459
+
460
+ Apply these rules whenever building or refactoring JJ-based custom elements:
461
+
462
+ 1. **Template-first component UI** — if component markup is static, use `fetchTemplate` + `setTemplate` (or shadow `init`) rather than building the UI imperatively in JS.
463
+ 2. **Keep routing/URL state outside UI components** — query params and history updates belong in page/controller code, not in reusable visual components.
464
+ 3. **Query with wrappers, not native DOM first** — prefer `find` / `findAll` on JJ wrappers before dropping to `.ref`.
465
+ 4. **Do not unwrap and re-wrap** — avoid `find(...).ref` followed by `JJHE.from(...)`; keep the wrapper value.
466
+ 5. **Use specific selectors for required nodes** — prefer selectors like `button#save` and `progress#step-progress` so selector intent replaces manual `instanceof` checks.
467
+ 6. **Keep one canonical state-update path** — for state like `step`, centralize validation/clamping/render/event dispatch in one code path; avoid split setter/private-method duplication unless clearly justified.
468
+ 7. **Use one initialization invariant** — if `isInitialized` exists and guarantees bound refs are ready, guard on that flag instead of repeating null checks for every bound field.
469
+ 8. **Prefer fluent assignment when binding handlers** — in setup code, chain `.on(...)` directly on `find(...)` where readable (for example assigning a button wrapper and click handler in one line).
470
+
471
+ ## Reference Docs
472
+
473
+ For framework migration or deep-dive patterns, load these on demand:
474
+
475
+ - `references/react-to-jj-translation.md`
476
+ - `references/vue-to-jj-translation.md`
477
+ - `references/svelte-to-jj-translation.md`
478
+ - `references/angular-to-jj-translation.md`
479
+ - `references/jquery-to-jj-translation.md`
480
+ - `references/lit-to-jj-translation.md`
481
+ - `references/web-components-patterns.md`
482
+ - `references/eventing-patterns.md`
483
+ - `references/querying-patterns.md`
484
+ - `references/css-improvements.md`
485
+ - `references/testing-with-jsdom.md`
486
+ - `references/security-and-html.md`
487
+ - `references/error-handling-patterns.md`
@@ -0,0 +1,76 @@
1
+ # Angular to JJ Translation
2
+
3
+ Angular uses a DI-driven component architecture with templates and decorators. JJ is library-level, not framework-level.
4
+
5
+ ## Mental mapping
6
+
7
+ | Angular concept | JJ equivalent |
8
+ | ---------------------------- | --------------------------------------------------------------- |
9
+ | `@Component` template | `JJHE.tree()` or external HTML with `fetchTemplate` |
10
+ | `@Input()` | `observedAttributes` + `attr2prop` + property setter |
11
+ | `@Output()` / `EventEmitter` | `triggerCustomEvent()` dispatching a `CustomEvent` |
12
+ | `ngOnInit` | `connectedCallback` |
13
+ | `ngOnDestroy` | `disconnectedCallback` |
14
+ | `ChangeDetectionRef` | Explicit render call in property setters |
15
+ | `*ngFor` | `el.addChildMap(items, fn)` |
16
+ | `*ngIf` | `el.hide()` / `el.show()` or conditional class via `setClasses` |
17
+ | DI services | Plain ES modules exporting shared state or event buses |
18
+
19
+ ## Input binding example
20
+
21
+ Angular:
22
+
23
+ ```typescript
24
+ @Input() userName: string = ''
25
+ ```
26
+
27
+ JJ:
28
+
29
+ ```js
30
+ static observedAttributes = ['user-name']
31
+ attributeChangedCallback(name, oldValue, newValue) {
32
+ attr2prop(this, name, oldValue, newValue)
33
+ }
34
+ get userName() { return this.#userName }
35
+ set userName(v) { this.#userName = String(v ?? ''); this.#render() }
36
+ ```
37
+
38
+ ## Output binding example
39
+
40
+ Angular:
41
+
42
+ ```typescript
43
+ @Output() selected = new EventEmitter<string>()
44
+ this.selected.emit(itemId)
45
+ ```
46
+
47
+ JJ:
48
+
49
+ ```js
50
+ JJHE.from(this).triggerCustomEvent('selected', itemId)
51
+ // JJ default: bubbles: true, composed: true — parent receives through shadow boundary
52
+ ```
53
+
54
+ ## No DI — use ES modules instead
55
+
56
+ Angular service:
57
+
58
+ ```typescript
59
+ @Injectable({ providedIn: 'root' })
60
+ class AuthService { … }
61
+ ```
62
+
63
+ JJ — just a module:
64
+
65
+ ```js
66
+ // auth.js
67
+ export const authState = { user: null }
68
+ export function login(user) {
69
+ authState.user = user
70
+ }
71
+ ```
72
+
73
+ ## Browser references
74
+
75
+ - Custom element lifecycle: https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks
76
+ - ES Modules: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules
@@ -0,0 +1,91 @@
1
+ # CSS Improvements
2
+
3
+ Use JJ for DOM wiring and rely on CSS standards for the styling system.
4
+
5
+ ## JJ style helpers with examples
6
+
7
+ ```js
8
+ // Single property
9
+ el.setStyle('color', 'var(--color-brand)')
10
+ el.setStyle('padding', '8px 16px')
11
+
12
+ // Batch — null/false removes the property
13
+ el.setStyles({
14
+ color: 'red',
15
+ padding: '8px',
16
+ border: null, // removes border
17
+ background: false, // removes background
18
+ })
19
+
20
+ // Remove properties
21
+ el.rmStyle('color', 'padding')
22
+
23
+ // Read a property
24
+ const val = el.getStyle('color') // '' when not set
25
+ ```
26
+
27
+ ## Class-driven styling (preferred)
28
+
29
+ Modify classes and keep visual logic in CSS:
30
+
31
+ ```js
32
+ el.addClass('is-active')
33
+ el.rmClass('is-loading')
34
+ el.swClass('is-expanded', isExpanded) // explicit: condition drives add/remove
35
+ el.swClass('is-expanded') // auto: flips current state
36
+ el.setClasses({ 'is-active': isActive, 'is-error': hasError })
37
+ el.setClass('card card--featured') // replaces entire className
38
+ ```
39
+
40
+ ## Text direction and advanced DOM state
41
+
42
+ ```js
43
+ el.setAttr('dir', 'rtl')
44
+ el.setAttr('lang', 'ar')
45
+ ```
46
+
47
+ ## CSS custom properties pierce Shadow DOM
48
+
49
+ Custom properties cascade through shadow boundaries. Define variables on the host or `:root`:
50
+
51
+ ```css
52
+ :root {
53
+ --btn-color: #0070f3;
54
+ }
55
+ ```
56
+
57
+ Inside shadow styles:
58
+
59
+ ```css
60
+ button {
61
+ background: var(--btn-color);
62
+ }
63
+ ```
64
+
65
+ ## Loading stylesheets
66
+
67
+ ```js
68
+ import { JJHE, fetchStyle } from 'jj'
69
+
70
+ const h = JJHE.tree
71
+
72
+ // Hint browser to start loading early
73
+ document.head.addChild(
74
+ h('link', {
75
+ href: import.meta.resolve('./theme.css'),
76
+ rel: 'preload',
77
+ as: 'style',
78
+ }).ref,
79
+ )
80
+
81
+ // Load as a CSSStyleSheet for adoptedStyleSheets
82
+ const sheet = await fetchStyle(import.meta.resolve('./theme.css'))
83
+ document.adoptedStyleSheets = [sheet]
84
+ ```
85
+
86
+ ## Browser references
87
+
88
+ - CSSStyleDeclaration: https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration
89
+ - CSS nesting: https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Nesting
90
+ - CSS custom properties: https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties
91
+ - adoptedStyleSheets: https://developer.mozilla.org/en-US/docs/Web/API/Document/adoptedStyleSheets