Skip to content

Commit 16d309b

Browse files
serpentbladeclaude
andcommitted
docs(260602-dv1): element-dependent config pattern + shadow-DOM selector warning
features.md gains an "Element-dependent config: the $onMount + r-if pattern" subsection under $refs — the ROZ123 wrong-way vs right-way example, plus the prefer-elements-over-selectors guidance for third-party libraries. flatpickr.md's rangePlugin recipe gains a warning callout: selector-string input resolves via document.querySelector, which cannot see inside shadow DOM and fails silently — pass the element constructed after mount instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 382b42f commit 16d309b

2 files changed

Lines changed: 32 additions & 0 deletions

File tree

docs/guide/features.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,34 @@ This is the integration story for component libraries that wrap vanilla-JS engin
550550

551551
Because `$refs` are only populated after mount, reading them in an eagerly-evaluated position — inside a `$computed(...)` body or `$watch` getter, or in a template binding / `{{ }}` interpolation / `r-if` / `r-show` / `r-for` iterable expression — is a compile error (`ROZ123`); read `$refs` inside `$onMount` (or any callback that runs after mount) instead.
552552

553+
### Element-dependent config: the `$onMount` + `r-if` pattern
554+
555+
Some vanilla-JS engines take a DOM element in their *configuration* (not just as their mount target) — flatpickr's `rangePlugin` second input, a Popper anchor element, a focus-trap container. The config has to be built **after** the element exists, and whatever consumes it has to wait for it:
556+
557+
```rozie
558+
<data>{ plugins: null }</data>
559+
560+
<script>
561+
import rangePlugin from 'flatpickr/dist/plugins/rangePlugin'
562+
563+
// ❌ ROZ123 — $refs is not populated yet when a $computed first evaluates:
564+
// const plugins = $computed(() => [rangePlugin({ input: $refs.endInput })])
565+
566+
// ✅ Build element-dependent config in $onMount, where $refs are live:
567+
$onMount(() => {
568+
$data.plugins = [rangePlugin({ input: $refs.endInput })]
569+
})
570+
</script>
571+
572+
<template>
573+
<!-- r-if gates the consumer until the config exists -->
574+
<Flatpickr r-if="$data.plugins" :plugins="$data.plugins" mode="range" />
575+
<input ref="endInput" />
576+
</template>
577+
```
578+
579+
Prefer passing **elements** over selector strings to third-party libraries wherever their API allows it. Libraries that resolve selector strings internally (flatpickr's `rangePlugin` does `document.querySelector(...)`) cannot see inside shadow DOM — so a selector that works on five targets silently finds nothing on Lit, where your component's template renders into a shadow root. Passing the `$refs` element sidesteps the lookup entirely and behaves identically on all six targets.
580+
553581
## `$snapshot()` — crossing into untyped JS
554582

555583
`$snapshot(x)` is the escape hatch for handing a reactive value to a library that mutates the value's property descriptors. The canonical case is Chart.js's data config: Chart.js internally calls `Object.defineProperty(data, ...)` to install reactive getters, and Svelte 5's `$state` Proxy raises `state_descriptors_fixed` rather than allowing the mutation. The other five targets unwrap to plain values at read time and don't have this problem.

docs/guide/flatpickr.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,10 @@ onMounted(() => {
350350

351351
`plugins` is **construction-time only** — flatpickr reads it once at init. To swap plugins live, re-key the component (see [Remount on construction-time-only changes](#remount-on-construction-time-only-changes)).
352352

353+
::: warning Pass the element, not a selector string
354+
`rangePlugin` also accepts a selector string (`input: '#second-input'`), but it resolves it with `document.querySelector(...)` — which **cannot see inside shadow DOM** and fails silently (a `console.warn`, not an error) when it finds nothing. A selector that works in a light-DOM app finds nothing when the inputs render inside a shadow root — the Lit build of any component, or any custom-element context. Constructing the plugin **after mount with the element itself** (as shown above) behaves identically everywhere. The same advice applies to any third-party option that accepts an element-or-selector.
355+
:::
356+
353357
### Custom parse/format
354358

355359
`parseDate` and `formatDate` hand flatpickr custom string↔Date functions — useful for formats flatpickr's token grammar can't express:

0 commit comments

Comments
 (0)