@wcstack/broadcast 1.16.0 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +49 -0
- package/README.md +51 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.esm.js +53 -0
- package/dist/index.esm.js.map +1 -1
- package/dist/index.esm.min.js +1 -1
- package/dist/index.esm.min.js.map +1 -1
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -146,6 +146,55 @@ post は DOM クリック(autoTrigger)または command-token から起動
|
|
|
146
146
|
<wcs-broadcast name="room" data-wcs="command.post: $command.send"></wcs-broadcast>
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
+
## `:state()` による CSS スタイリング
|
|
150
|
+
|
|
151
|
+
`<wcs-broadcast>` は 1 つの boolean 出力ステートを
|
|
152
|
+
[`ElementInternals` の `CustomStateSet`](https://developer.mozilla.org/ja/docs/Web/API/CustomStateSet)
|
|
153
|
+
に反映します。そのため `data-wcs` バインディングやクラスの手動トグルなしに、CSS の
|
|
154
|
+
`:state()` 疑似クラスで直接スタイリングできます。`<wcs-broadcast>` には
|
|
155
|
+
`loading`/`connected` のような boolean 出力は無く(post とチャンネルのオープンは
|
|
156
|
+
同期的です)、反映されるのは `error` のみです。
|
|
157
|
+
|
|
158
|
+
| ステート | on になる条件 |
|
|
159
|
+
|----------|----------------|
|
|
160
|
+
| `error` | `wcs-broadcast:error` が非 `null` の detail で発火(`null` でクリア) |
|
|
161
|
+
|
|
162
|
+
```css
|
|
163
|
+
form:has(wcs-broadcast:state(error)) .banner { display: block; }
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
属性やクラスと異なり `:state()` は要素の外部から書き込めないため、この出力ステートが
|
|
167
|
+
入力と混同される心配がありません。
|
|
168
|
+
|
|
169
|
+
**対応ブラウザ**(新構文 `:state(x)`): Chrome/Edge 125+、Safari 17.4+、Firefox 126+。
|
|
170
|
+
非対応の環境ではステートが一切 set されないだけです — `:state()` セレクタがマッチしなく
|
|
171
|
+
なりますが、`<wcs-broadcast>` 自体は通常どおり動作し続けます(graceful degradation・never-throw)。
|
|
172
|
+
|
|
173
|
+
**SSR:** `:state()` は HTML にシリアライズできないため、サーバーレンダリングされた
|
|
174
|
+
マークアップの初期ペイントにはこれらのステートは乗りません(`@wcstack/server` は無改変)。
|
|
175
|
+
ハイドレーション前の見た目を制御したい場合は、代わりに `wcs-broadcast:not(:defined)` と組み合わせてください。
|
|
176
|
+
|
|
177
|
+
### デバッグ
|
|
178
|
+
|
|
179
|
+
カスタムステートは DevTools の Elements パネルには表示されず、`attachInternals()`
|
|
180
|
+
は同一要素に 2 回呼べないため、コンソールから直接覗く手段がありません。そのための
|
|
181
|
+
デバッグ専用の補助を 2 つ用意しています:
|
|
182
|
+
|
|
183
|
+
- `el.debugStates` — 現在 on になっているステート名の**スナップショット**配列
|
|
184
|
+
(例: `["error"]`)。`wc-bindable` の一部ではなく(バインド対象ではない)、
|
|
185
|
+
形状も契約として保証されません — デバッグ用途にのみ使ってください。
|
|
186
|
+
- `debug-states` 属性(opt-in・既定 OFF)は、ステート変化を要素の
|
|
187
|
+
`data-wcs-state-error` 属性にミラーします。Elements パネルを開いておけば、
|
|
188
|
+
トグルのたびにハイライトされます:
|
|
189
|
+
|
|
190
|
+
```html
|
|
191
|
+
<wcs-broadcast name="room" debug-states></wcs-broadcast>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**CSS は `data-wcs-state-*` ではなく `:state()` に書いてください。** ミラーされた
|
|
195
|
+
属性は、DevTools を開いた状態でステート変化を可視化するためだけのものであり、
|
|
196
|
+
スタイリング用の正式なフックではありません。
|
|
197
|
+
|
|
149
198
|
## 注意点と制約
|
|
150
199
|
|
|
151
200
|
- **自己除外は意図的。** あるコンテキストは自分自身の post を決して受信しません — これは BroadcastChannel の契約であり、バグではありません。往復を見るには、第 2 のコンテキスト(タブ/iframe/worker、または同じチャンネル名の第 2 の `<wcs-broadcast>`)を listen させてください。*同じ*タブ内の 2 つの `<wcs-broadcast name="x">` 要素は互いに受信します(別々のチャンネルオブジェクトだから)。単一の要素が自分自身に話しかける場合だけは受信しません。
|
package/README.md
CHANGED
|
@@ -146,6 +146,57 @@ State-driven invocation uses the command-token protocol:
|
|
|
146
146
|
<wcs-broadcast name="room" data-wcs="command.post: $command.send"></wcs-broadcast>
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
+
## CSS styling with `:state()`
|
|
150
|
+
|
|
151
|
+
`<wcs-broadcast>` reflects one boolean output state onto its
|
|
152
|
+
[`ElementInternals` `CustomStateSet`](https://developer.mozilla.org/en-US/docs/Web/API/CustomStateSet),
|
|
153
|
+
so you can style it directly from CSS with the `:state()` pseudo-class — no
|
|
154
|
+
`data-wcs` binding or extra class toggling required. `<wcs-broadcast>` has no
|
|
155
|
+
`loading`/`connected`-style boolean output (posting and opening a channel are
|
|
156
|
+
synchronous), so `error` is the only reflected state.
|
|
157
|
+
|
|
158
|
+
| State | On when |
|
|
159
|
+
|-------|---------|
|
|
160
|
+
| `error` | `wcs-broadcast:error` fires with a non-`null` detail (cleared on `null`) |
|
|
161
|
+
|
|
162
|
+
```css
|
|
163
|
+
form:has(wcs-broadcast:state(error)) .banner { display: block; }
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Unlike attributes or classes, `:state()` cannot be written from outside the
|
|
167
|
+
element, so there is no risk of confusing this output state with an input.
|
|
168
|
+
|
|
169
|
+
**Browser support** (`:state(x)` syntax): Chrome/Edge 125+, Safari 17.4+,
|
|
170
|
+
Firefox 126+. In older browsers the states are simply never set — `:state()`
|
|
171
|
+
selectors never match, but `<wcs-broadcast>` itself keeps working normally
|
|
172
|
+
(graceful degradation, never-throw).
|
|
173
|
+
|
|
174
|
+
**SSR**: `:state()` cannot be serialized into HTML, so server-rendered markup
|
|
175
|
+
never carries these states on first paint (`@wcstack/server` is unaffected).
|
|
176
|
+
If you need to style the pre-hydration gap, pair your rule with
|
|
177
|
+
`wcs-broadcast:not(:defined)` instead.
|
|
178
|
+
|
|
179
|
+
### Debugging
|
|
180
|
+
|
|
181
|
+
Custom states are invisible in DevTools' Elements panel and `attachInternals()`
|
|
182
|
+
cannot be called twice, so there is no console way to inspect them directly.
|
|
183
|
+
Two debug-only aids are provided for that:
|
|
184
|
+
|
|
185
|
+
- `el.debugStates` — a **snapshot** array of the currently-on state names
|
|
186
|
+
(e.g. `["error"]`). It is not part of `wc-bindable` (not a bind target)
|
|
187
|
+
and its shape is not a guaranteed contract — use it for debugging only.
|
|
188
|
+
- The `debug-states` attribute (opt-in, default off) mirrors state changes
|
|
189
|
+
onto a `data-wcs-state-error` attribute on the element, so the Elements
|
|
190
|
+
panel highlights it as it toggles:
|
|
191
|
+
|
|
192
|
+
```html
|
|
193
|
+
<wcs-broadcast name="room" debug-states></wcs-broadcast>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Write your CSS against `:state()`, not `data-wcs-state-*`.** The mirrored
|
|
197
|
+
attribute exists purely to make state changes visible while debugging with
|
|
198
|
+
DevTools open; it is not a supported styling hook.
|
|
199
|
+
|
|
149
200
|
## Notes & limitations
|
|
150
201
|
|
|
151
202
|
- **Self-exclusion is intentional.** A context never receives its own posts — this is the BroadcastChannel contract, not a bug. To see a round trip, have a second context (tab/iframe/worker, or a second `<wcs-broadcast>` on the same channel name) listening. Two `<wcs-broadcast name="x">` elements in the *same* tab do hear each other (they are distinct channel objects); only a single element talking to itself does not.
|
package/dist/index.d.ts
CHANGED
|
@@ -181,7 +181,11 @@ declare class WcsBroadcast extends HTMLElement {
|
|
|
181
181
|
static get observedAttributes(): string[];
|
|
182
182
|
private _core;
|
|
183
183
|
private _connectedCallbackPromise;
|
|
184
|
+
private _internals;
|
|
184
185
|
constructor();
|
|
186
|
+
get debugStates(): string[];
|
|
187
|
+
private _initInternals;
|
|
188
|
+
private _wireStates;
|
|
185
189
|
get connectedCallbackPromise(): Promise<void>;
|
|
186
190
|
get name(): string;
|
|
187
191
|
set name(value: string);
|
package/dist/index.esm.js
CHANGED
|
@@ -393,9 +393,62 @@ class WcsBroadcast extends HTMLElement {
|
|
|
393
393
|
static get observedAttributes() { return ["name"]; }
|
|
394
394
|
_core;
|
|
395
395
|
_connectedCallbackPromise = Promise.resolve();
|
|
396
|
+
_internals = null;
|
|
396
397
|
constructor() {
|
|
397
398
|
super();
|
|
398
399
|
this._core = new BroadcastCore(this);
|
|
400
|
+
this._internals = this._initInternals();
|
|
401
|
+
this._wireStates({
|
|
402
|
+
"wcs-broadcast:error": (d) => ({ error: d != null }),
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
// CSS state reflection (:state()) — debug-only snapshot getter. NOT part of
|
|
406
|
+
// wc-bindable (not a bind target); see README "CSS styling with :state()".
|
|
407
|
+
// MUST NOT return the live CustomStateSet (that would let callers write
|
|
408
|
+
// states from outside, defeating the point of :state() being read-only).
|
|
409
|
+
get debugStates() {
|
|
410
|
+
return this._internals ? [...this._internals.states] : [];
|
|
411
|
+
}
|
|
412
|
+
_initInternals() {
|
|
413
|
+
// never-throw (async-io-node-guidelines.md §3.6): attachInternals is absent
|
|
414
|
+
// in happy-dom / older environments, and pre-125 Chromium rejects
|
|
415
|
+
// non-dashed state names from states.add() (probed and discarded here).
|
|
416
|
+
// Either case silently disables reflection — the component still works,
|
|
417
|
+
// it just doesn't expose :state() selectors.
|
|
418
|
+
try {
|
|
419
|
+
if (typeof this.attachInternals !== "function")
|
|
420
|
+
return null;
|
|
421
|
+
const internals = this.attachInternals();
|
|
422
|
+
internals.states.add("wcs-probe");
|
|
423
|
+
internals.states.delete("wcs-probe");
|
|
424
|
+
return internals;
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
_wireStates(map) {
|
|
431
|
+
if (this._internals === null)
|
|
432
|
+
return;
|
|
433
|
+
const states = this._internals.states;
|
|
434
|
+
for (const [event, toStates] of Object.entries(map)) {
|
|
435
|
+
this.addEventListener(event, (e) => {
|
|
436
|
+
const debug = this.hasAttribute("debug-states");
|
|
437
|
+
for (const [name, on] of Object.entries(toStates(e.detail))) {
|
|
438
|
+
try {
|
|
439
|
+
if (on) {
|
|
440
|
+
states.add(name);
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
states.delete(name);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
catch { /* never-throw */ }
|
|
447
|
+
if (debug)
|
|
448
|
+
this.toggleAttribute(`data-wcs-state-${name}`, on);
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
}
|
|
399
452
|
}
|
|
400
453
|
get connectedCallbackPromise() {
|
|
401
454
|
return this._connectedCallbackPromise;
|
package/dist/index.esm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.esm.js","sources":["../src/config.ts","../src/core/BroadcastCore.ts","../src/autoTrigger.ts","../src/components/Broadcast.ts","../src/registerComponents.ts","../src/bootstrapBroadcast.ts"],"sourcesContent":["import { IConfig, IWritableConfig } from \"./types.js\";\n\ninterface IInternalConfig extends IConfig {\n autoTrigger: boolean;\n triggerAttribute: string;\n tagNames: {\n broadcast: string;\n };\n}\n\nconst _config: IInternalConfig = {\n autoTrigger: true,\n triggerAttribute: \"data-broadcast-target\",\n tagNames: {\n broadcast: \"wcs-broadcast\",\n },\n};\n\nfunction deepFreeze<T>(obj: T): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n Object.freeze(obj);\n for (const key of Object.keys(obj)) {\n deepFreeze((obj as Record<string, unknown>)[key]);\n }\n return obj;\n}\n\nfunction deepClone<T>(obj: T): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n const clone: Record<string, unknown> = {};\n for (const key of Object.keys(obj)) {\n clone[key] = deepClone((obj as Record<string, unknown>)[key]);\n }\n return clone as T;\n}\n\nlet frozenConfig: IConfig | null = null;\n\n// Live reference to the mutable internal config: reads always reflect the latest\n// setConfig() call. The readonly IConfig type only blocks callers from writing\n// through it — the underlying object still changes. If you need a stable,\n// frozen snapshot that won't move under you, use getConfig() instead.\nexport const config: IConfig = _config as IConfig;\n\nexport function getConfig(): IConfig {\n if (!frozenConfig) {\n frozenConfig = deepFreeze(deepClone(_config));\n }\n return frozenConfig;\n}\n\nexport function setConfig(partialConfig: IWritableConfig): void {\n if (typeof partialConfig.autoTrigger === \"boolean\") {\n _config.autoTrigger = partialConfig.autoTrigger;\n }\n if (typeof partialConfig.triggerAttribute === \"string\") {\n _config.triggerAttribute = partialConfig.triggerAttribute;\n }\n if (partialConfig.tagNames) {\n Object.assign(_config.tagNames, partialConfig.tagNames);\n }\n frozenConfig = null;\n}\n","import { IWcBindable, WcsBroadcastErrorDetail } from \"../types.js\";\n\n/**\n * Headless cross-tab messaging primitive. A thin, framework-agnostic wrapper\n * around the BroadcastChannel API exposed through the wc-bindable protocol.\n *\n * BroadcastChannel is a same-origin pub/sub bus: every context (tab, iframe,\n * worker) that opens a channel with the same `name` receives every other\n * context's posts — but NOT its own. This self-exclusion is the whole point:\n * `post` is a `state → element` action (command-token) and an incoming\n * `message` is an `element → state` notification (event-token), but the two\n * only close the loop *across* a context boundary. Within a single tab a lone\n * `<wcs-broadcast>` never hears itself; open the page in two tabs to see the\n * round-trip.\n *\n * Unlike WebSocketCore there is no connection state, no reconnect, and no wire\n * encoding: a channel is \"open\" the moment it is constructed, and payloads ride\n * the browser's structured clone (objects pass through as-is, no JSON\n * round-trip). The only failure surfaces are a non-cloneable `post`\n * (`DataCloneError`), a `messageerror` (a peer posted something this context\n * cannot deserialize), and an absent `BroadcastChannel` constructor\n * (`unsupported`). All three flow through the `error` property — the Core never\n * throws — symmetrical with FetchCore / ClipboardCore.\n */\nexport class BroadcastCore extends EventTarget {\n static wcBindable: IWcBindable = {\n protocol: \"wc-bindable\",\n version: 1,\n properties: [\n { name: \"message\", event: \"wcs-broadcast:message\" },\n { name: \"error\", event: \"wcs-broadcast:error\" },\n ],\n commands: [\n { name: \"open\" },\n { name: \"post\" },\n { name: \"close\" },\n ],\n };\n\n private _target: EventTarget;\n private _channel: BroadcastChannel | null = null;\n private _name: string | null = null;\n private _message: any = null;\n private _error: WcsBroadcastErrorDetail | null = null;\n // Generation guard (§3.4): bumped on dispose(). An incoming message /\n // messageerror that fires after the Shell disconnected (a peer posted between\n // disconnect and the channel actually closing, or a queued event drains late)\n // has a stale `gen` and MUST NOT write state to a torn-down element. A boolean\n // flag is insufficient: dispose→reconnect would let a stale event slip through.\n private _gen = 0;\n // SSR (§3.8): a channel opens synchronously (no asynchronous probe to await),\n // so readiness is immediate.\n private _ready: Promise<void> = Promise.resolve();\n\n constructor(target?: EventTarget) {\n super();\n this._target = target ?? this;\n }\n\n get ready(): Promise<void> {\n return this._ready;\n }\n\n get message(): any {\n return this._message;\n }\n\n get error(): WcsBroadcastErrorDetail | null {\n return this._error;\n }\n\n // --- Lifecycle (§3.5) ---\n\n // observe() establishes monitoring. BroadcastChannel is command-driven (the\n // Shell calls open(name) from connectedCallback / attributeChangedCallback),\n // so there is no subscription for observe() to set up here: it is an idempotent\n // no-op that resolves once ready. It exists for skeleton symmetry with the\n // monitor-style nodes so a host can uniformly await observe() == ready.\n observe(): Promise<void> {\n return this._ready;\n }\n\n // --- State setters with event dispatch ---\n\n // Deliberately NO same-value guard (unlike `error` below). A received message\n // is an event, not idempotent state: a peer posting the same value twice is\n // two distinct occurrences and must re-fire wcs-broadcast:message each time so\n // a `message:` binding and any `eventToken.message:` subscriber see both.\n private _setMessage(message: any): void {\n this._message = message;\n this._target.dispatchEvent(new CustomEvent(\"wcs-broadcast:message\", {\n detail: message,\n bubbles: true,\n }));\n }\n\n private _setError(error: WcsBroadcastErrorDetail | null): void {\n // Same-value guard. `error` has no derived state, so suppressing redundant\n // null→null dispatches (e.g. a successful open clearing an already-null\n // error) avoids spurious events. Reference identity is sufficient: each\n // failure builds a fresh object, and the clear path always passes null.\n if (this._error === error) return;\n this._error = error;\n this._target.dispatchEvent(new CustomEvent(\"wcs-broadcast:error\", {\n detail: error,\n bubbles: true,\n }));\n }\n\n // --- Public API ---\n\n /**\n * Join the named channel. Any previously-open channel is closed first, so\n * calling `open()` again switches channels. When the BroadcastChannel\n * constructor is unavailable this surfaces an `unsupported` error and leaves\n * the Core channel-less (a later `post()` then errors loudly rather than\n * silently dropping).\n */\n open(name: string): void {\n if (!this._hasBroadcastChannel()) {\n this._setError(this._unsupportedError());\n return;\n }\n // Idempotent on the same channel: re-opening the channel we are already on\n // is pure churn (BroadcastChannel has no reconnect semantics). This also\n // absorbs the custom-element *upgrade* path — when a connected element with\n // a `name` attribute is upgraded (autoloader defines the tag after the\n // markup exists), the spec fires attributeChangedCallback (isConnected ===\n // true) *and* connectedCallback, so the Shell calls open() twice. Without\n // this guard that would create a channel and immediately tear it down.\n if (this._channel && this._name === name) return;\n this._closeChannel();\n this._setError(null);\n // Capture the generation for this channel (§3.4). The listeners below close\n // over `gen`; an event that fires after dispose() (which bumps _gen) is\n // recognised as stale and dropped without writing state to a torn-down\n // element. The handlers are stored so _closeChannel() can remove them by the\n // same reference.\n const gen = ++this._gen;\n const channel = new BroadcastChannel(name);\n this._onMessage = (event: MessageEvent): void => {\n if (gen !== this._gen) return;\n this._setMessage(event.data);\n };\n // Fired when a peer posted a value this context cannot deserialize. The event\n // carries no usable payload, so report a synthetic DataError.\n this._onMessageError = (): void => {\n if (gen !== this._gen) return;\n this._setError({\n name: \"DataError\",\n message: \"Failed to deserialize a message received on the channel.\",\n });\n };\n channel.addEventListener(\"message\", this._onMessage);\n channel.addEventListener(\"messageerror\", this._onMessageError);\n this._channel = channel;\n this._name = name;\n }\n\n /**\n * Post a structured-cloneable value to every other context on the channel.\n * The local context never receives it (self-exclusion). Never throws:\n * a non-cloneable value surfaces as a `DataCloneError` through `error`, and\n * posting with no open channel surfaces an `InvalidStateError`.\n */\n post(data: any): void {\n if (!this._hasBroadcastChannel()) {\n this._setError(this._unsupportedError());\n return;\n }\n if (!this._channel) {\n this._setError({\n name: \"InvalidStateError\",\n message: \"Channel is not open. Call open(name) before post().\",\n });\n return;\n }\n try {\n this._channel.postMessage(data);\n } catch (err) {\n this._setError(this._normalizeError(err));\n }\n }\n\n /** Leave the channel. Idempotent — a no-op when no channel is open. */\n close(): void {\n this._closeChannel();\n }\n\n /**\n * Tear the Core down for a disconnected Shell: close the channel and reset the\n * error shadow silently (no dispatch on a torn-down element). A later\n * reconnect re-opens via the Shell's connectedCallback.\n *\n * Asymmetry by design: `_message` is deliberately NOT reset. `error` is\n * transient connection state — a stale error from a previous channel would be\n * misleading after a reconnect, so it is cleared. `message` is the last value\n * received (an event payload), not connection state; it is retained as the\n * Core's last-known datum so a binding still reads it across a disconnect/\n * reconnect, and it is naturally overwritten by the next incoming message.\n */\n dispose(): void {\n // Bump the generation first (§3.4) so any message/messageerror that drains\n // after teardown is recognised as stale, then close the channel and reset the\n // error shadow silently.\n this._gen++;\n this._closeChannel();\n this._error = null;\n }\n\n // --- Internal ---\n\n // Per-channel listeners, (re)created in open() so each closes over its own\n // generation (§3.4). null while no channel is open; the real handlers are\n // installed by open() and removed by the same reference in _closeChannel().\n private _onMessage: ((event: MessageEvent) => void) | null = null;\n private _onMessageError: (() => void) | null = null;\n\n private _closeChannel(): void {\n if (!this._channel) return;\n this._channel.removeEventListener(\"message\", this._onMessage!);\n this._channel.removeEventListener(\"messageerror\", this._onMessageError!);\n this._channel.close();\n this._channel = null;\n this._name = null;\n this._onMessage = null;\n this._onMessageError = null;\n }\n\n private _hasBroadcastChannel(): boolean {\n return typeof BroadcastChannel !== \"undefined\";\n }\n\n private _normalizeError(err: unknown): WcsBroadcastErrorDetail {\n if (err instanceof Error) {\n // DOMException is an Error subclass; its `name` (DataCloneError, etc.) is\n // the meaningful discriminator for consumers switching on failure kind.\n return { name: err.name, message: err.message };\n }\n return { name: \"Error\", message: String(err) };\n }\n\n private _unsupportedError(): WcsBroadcastErrorDetail {\n return {\n name: \"NotSupportedError\",\n message: \"BroadcastChannel is not available in this environment.\",\n };\n }\n}\n","import { config } from \"./config.js\";\nimport type { WcsBroadcast } from \"./components/Broadcast.js\";\n\nlet registered = false;\n\n// Attribute names for the optional post-on-click DOM trigger (clipboard.js-style\n// DX). The element carrying `data-broadcast-target` points at a <wcs-broadcast>\n// by id; the text to post comes from either a literal `data-broadcast-text` or\n// a `data-broadcast-from` CSS selector resolving to a source element.\nconst TEXT_ATTRIBUTE = \"data-broadcast-text\";\nconst FROM_ATTRIBUTE = \"data-broadcast-from\";\n\nfunction resolveText(triggerElement: Element): string | null {\n // Literal text wins when present (including an empty string — posting \"\" is a\n // legitimate request). The `?? \"\"` right-hand side is defensive and\n // unreachable: hasAttribute() just returned true, so getAttribute() cannot be\n // null here. It exists only to satisfy the `string | null` return type — do\n // not chase coverage on it (the DOM contract makes the null branch impossible).\n if (triggerElement.hasAttribute(TEXT_ATTRIBUTE)) {\n return triggerElement.getAttribute(TEXT_ATTRIBUTE) ?? \"\";\n }\n const selector = triggerElement.getAttribute(FROM_ATTRIBUTE);\n if (!selector) return null;\n // A user-authored selector can be syntactically invalid (e.g. `[data-*` or a\n // bare `:not()`), which makes querySelector throw a SyntaxError. Swallow it\n // and treat the source as unresolvable — the same \"nothing to post\" path as a\n // selector that matches no element — so one bad attribute never crashes the\n // document-level click handler and kills autoTrigger for the whole tab.\n let source: Element | null;\n try {\n source = document.querySelector(selector);\n } catch {\n return null;\n }\n if (!source) return null;\n // Read a form control's `value`; fall back to text content. A bare\n // `\"value\" in source` check is too broad — it also matches <button>,\n // <li value>, <progress>, etc. (which carry an unrelated `value`), posting the\n // wrong thing. Narrow to the text-bearing controls a user actually points\n // `data-broadcast-from` at; everything else falls through to textContent.\n if (\n source instanceof HTMLInputElement ||\n source instanceof HTMLTextAreaElement ||\n source instanceof HTMLSelectElement\n ) {\n return source.value;\n }\n // `?? \"\"` is defensive: per the DOM spec only Document / DocumentType /\n // Notation nodes have a null `textContent`, and querySelector only ever\n // returns an Element (whose textContent is always a string). The branch is\n // therefore unreachable in practice and kept solely for the `string | null`\n // type — not worth a contrived test.\n return source.textContent ?? \"\";\n}\n\nfunction handleClick(event: Event): void {\n const target = event.target;\n if (!(target instanceof Element)) return;\n\n const triggerElement = target.closest<Element>(`[${config.triggerAttribute}]`);\n if (!triggerElement) return;\n\n const broadcastId = triggerElement.getAttribute(config.triggerAttribute);\n if (!broadcastId) return;\n\n // Resolve the registered constructor at call time instead of importing\n // WcsBroadcast as a value (avoids a components ⇄ autoTrigger import cycle:\n // Broadcast.connectedCallback() calls registerAutoTrigger()). instanceof\n // against the customElements registry keeps the same identity guarantee.\n const BroadcastCtor = customElements.get(config.tagNames.broadcast);\n const broadcastElement = document.getElementById(broadcastId);\n if (!BroadcastCtor || !(broadcastElement instanceof BroadcastCtor)) return;\n\n const text = resolveText(triggerElement);\n // No resolvable source: leave the click alone (do not preventDefault) so the\n // element's default action is unaffected.\n if (text === null) return;\n\n // Suppress the default action so a post can run without navigating. Intentional:\n // do not attach data-broadcast-target to an element whose default action you\n // also want (a real <a href> link). See README \"Optional DOM Triggering\".\n event.preventDefault();\n (broadcastElement as WcsBroadcast).post(text);\n}\n\nexport function registerAutoTrigger(): void {\n if (registered) return;\n registered = true;\n document.addEventListener(\"click\", handleClick);\n}\n\nexport function unregisterAutoTrigger(): void {\n if (!registered) return;\n registered = false;\n document.removeEventListener(\"click\", handleClick);\n}\n","import { config } from \"../config.js\";\nimport { IWcBindable, WcsBroadcastErrorDetail } from \"../types.js\";\nimport { BroadcastCore } from \"../core/BroadcastCore.js\";\nimport { registerAutoTrigger } from \"../autoTrigger.js\";\n\n// Named WcsBroadcast (not `Broadcast`) to match the <wcs-clipboard> WcsClipboard\n// / <wcs-ws> WcsWebSocket convention and avoid shadowing any global.\nexport class WcsBroadcast extends HTMLElement {\n // SSR (§4.4): the channel opens synchronously in connectedCallback, so the\n // Core's observe() resolves immediately; we still expose connectedCallbackPromise\n // so a state binder can uniformly await readiness before snapshotting.\n static hasConnectedCallbackPromise = true;\n static wcBindable: IWcBindable = {\n ...BroadcastCore.wcBindable,\n // Shell-level settable surface. `name` selects the channel; `manual`\n // suppresses auto-open on connect. There is no momentary `post` property:\n // posting needs an argument (the payload), so element actions run via\n // command-token (`command.post: $command.ping`) or the DOM autoTrigger, not\n // a value-derived setter — keeping `post` a plain command keeps the\n // command-token wiring (`command.post:`) readable.\n inputs: [\n { name: \"name\", attribute: \"name\" },\n { name: \"manual\", attribute: \"manual\" },\n ],\n // Commands are identical to the Core's — no rename needed since the `name` /\n // `manual` attribute accessors do not collide with open/post/close.\n commands: BroadcastCore.wcBindable.commands,\n };\n static get observedAttributes(): string[] { return [\"name\"]; }\n\n private _core: BroadcastCore;\n private _connectedCallbackPromise: Promise<void> = Promise.resolve();\n\n constructor() {\n super();\n this._core = new BroadcastCore(this);\n }\n\n get connectedCallbackPromise(): Promise<void> {\n return this._connectedCallbackPromise;\n }\n\n // --- Attribute accessors ---\n\n get name(): string {\n return this.getAttribute(\"name\") || \"\";\n }\n\n set name(value: string) {\n this.setAttribute(\"name\", value);\n }\n\n get manual(): boolean {\n return this.hasAttribute(\"manual\");\n }\n\n set manual(value: boolean) {\n if (value) {\n this.setAttribute(\"manual\", \"\");\n } else {\n this.removeAttribute(\"manual\");\n }\n }\n\n // --- Core delegated getters ---\n\n get message(): any {\n return this._core.message;\n }\n\n get error(): WcsBroadcastErrorDetail | null {\n return this._core.error;\n }\n\n // --- Commands ---\n\n open(): void {\n if (this.name) {\n this._core.open(this.name);\n }\n }\n\n post(data: any): void {\n this._core.post(data);\n }\n\n close(): void {\n this._core.close();\n }\n\n // --- Lifecycle ---\n\n attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null): void {\n if (name === \"name\" && this.isConnected && !this.manual && newValue) {\n this._core.open(newValue);\n }\n }\n\n connectedCallback(): void {\n this.style.display = \"none\";\n if (config.autoTrigger) {\n registerAutoTrigger();\n }\n if (!this.manual && this.name) {\n this._core.open(this.name);\n }\n // SSR (§4.4): expose the Core's readiness as connectedCallbackPromise. The\n // channel opens synchronously above, so observe() resolves immediately.\n this._connectedCallbackPromise = this._core.observe();\n }\n\n disconnectedCallback(): void {\n // Deliberately does NOT call unregisterAutoTrigger(). The autoTrigger click\n // listener is a single process-wide document listener (registerAutoTrigger\n // is idempotent), shared by every <wcs-broadcast> on the page — not owned by\n // this element. Tearing it down when the last element disconnects would\n // break a later-inserted trigger, so it is intentionally left installed for\n // the document's lifetime (one passive listener, negligible cost). This\n // mirrors <wcs-clipboard>, which registers but never unregisters either.\n // unregisterAutoTrigger stays exported purely as a symmetric teardown hook\n // for tests / advanced manual control; the production lifecycle never calls\n // it.\n this._core.dispose();\n }\n}\n","import { WcsBroadcast } from \"./components/Broadcast.js\";\nimport { config } from \"./config.js\";\n\nexport function registerComponents(): void {\n if (!customElements.get(config.tagNames.broadcast)) {\n customElements.define(config.tagNames.broadcast, WcsBroadcast);\n }\n}\n","import { setConfig } from \"./config.js\";\nimport { registerComponents } from \"./registerComponents.js\";\nimport { IWritableConfig } from \"./types.js\";\n\nexport function bootstrapBroadcast(userConfig?: IWritableConfig): void {\n if (userConfig) {\n setConfig(userConfig);\n }\n registerComponents();\n}\n"],"names":[],"mappings":"AAUA,MAAM,OAAO,GAAoB;AAC/B,IAAA,WAAW,EAAE,IAAI;AACjB,IAAA,gBAAgB,EAAE,uBAAuB;AACzC,IAAA,QAAQ,EAAE;AACR,QAAA,SAAS,EAAE,eAAe;AAC3B,KAAA;CACF;AAED,SAAS,UAAU,CAAI,GAAM,EAAA;AAC3B,IAAA,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ;AAAE,QAAA,OAAO,GAAG;AACvD,IAAA,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC;IAClB,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;AAClC,QAAA,UAAU,CAAE,GAA+B,CAAC,GAAG,CAAC,CAAC;IACnD;AACA,IAAA,OAAO,GAAG;AACZ;AAEA,SAAS,SAAS,CAAI,GAAM,EAAA;AAC1B,IAAA,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ;AAAE,QAAA,OAAO,GAAG;IACvD,MAAM,KAAK,GAA4B,EAAE;IACzC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;QAClC,KAAK,CAAC,GAAG,CAAC,GAAG,SAAS,CAAE,GAA+B,CAAC,GAAG,CAAC,CAAC;IAC/D;AACA,IAAA,OAAO,KAAU;AACnB;AAEA,IAAI,YAAY,GAAmB,IAAI;AAEvC;AACA;AACA;AACA;AACO,MAAM,MAAM,GAAY,OAAkB;SAEjC,SAAS,GAAA;IACvB,IAAI,CAAC,YAAY,EAAE;QACjB,YAAY,GAAG,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAC/C;AACA,IAAA,OAAO,YAAY;AACrB;AAEM,SAAU,SAAS,CAAC,aAA8B,EAAA;AACtD,IAAA,IAAI,OAAO,aAAa,CAAC,WAAW,KAAK,SAAS,EAAE;AAClD,QAAA,OAAO,CAAC,WAAW,GAAG,aAAa,CAAC,WAAW;IACjD;AACA,IAAA,IAAI,OAAO,aAAa,CAAC,gBAAgB,KAAK,QAAQ,EAAE;AACtD,QAAA,OAAO,CAAC,gBAAgB,GAAG,aAAa,CAAC,gBAAgB;IAC3D;AACA,IAAA,IAAI,aAAa,CAAC,QAAQ,EAAE;QAC1B,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,aAAa,CAAC,QAAQ,CAAC;IACzD;IACA,YAAY,GAAG,IAAI;AACrB;;AC5DA;;;;;;;;;;;;;;;;;;;;;AAqBG;AACG,MAAO,aAAc,SAAQ,WAAW,CAAA;IAC5C,OAAO,UAAU,GAAgB;AAC/B,QAAA,QAAQ,EAAE,aAAa;AACvB,QAAA,OAAO,EAAE,CAAC;AACV,QAAA,UAAU,EAAE;AACV,YAAA,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,uBAAuB,EAAE;AACnD,YAAA,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,qBAAqB,EAAE;AAChD,SAAA;AACD,QAAA,QAAQ,EAAE;YACR,EAAE,IAAI,EAAE,MAAM,EAAE;YAChB,EAAE,IAAI,EAAE,MAAM,EAAE;YAChB,EAAE,IAAI,EAAE,OAAO,EAAE;AAClB,SAAA;KACF;AAEO,IAAA,OAAO;IACP,QAAQ,GAA4B,IAAI;IACxC,KAAK,GAAkB,IAAI;IAC3B,QAAQ,GAAQ,IAAI;IACpB,MAAM,GAAmC,IAAI;;;;;;IAM7C,IAAI,GAAG,CAAC;;;AAGR,IAAA,MAAM,GAAkB,OAAO,CAAC,OAAO,EAAE;AAEjD,IAAA,WAAA,CAAY,MAAoB,EAAA;AAC9B,QAAA,KAAK,EAAE;AACP,QAAA,IAAI,CAAC,OAAO,GAAG,MAAM,IAAI,IAAI;IAC/B;AAEA,IAAA,IAAI,KAAK,GAAA;QACP,OAAO,IAAI,CAAC,MAAM;IACpB;AAEA,IAAA,IAAI,OAAO,GAAA;QACT,OAAO,IAAI,CAAC,QAAQ;IACtB;AAEA,IAAA,IAAI,KAAK,GAAA;QACP,OAAO,IAAI,CAAC,MAAM;IACpB;;;;;;;IASA,OAAO,GAAA;QACL,OAAO,IAAI,CAAC,MAAM;IACpB;;;;;;AAQQ,IAAA,WAAW,CAAC,OAAY,EAAA;AAC9B,QAAA,IAAI,CAAC,QAAQ,GAAG,OAAO;QACvB,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,uBAAuB,EAAE;AAClE,YAAA,MAAM,EAAE,OAAO;AACf,YAAA,OAAO,EAAE,IAAI;AACd,SAAA,CAAC,CAAC;IACL;AAEQ,IAAA,SAAS,CAAC,KAAqC,EAAA;;;;;AAKrD,QAAA,IAAI,IAAI,CAAC,MAAM,KAAK,KAAK;YAAE;AAC3B,QAAA,IAAI,CAAC,MAAM,GAAG,KAAK;QACnB,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,qBAAqB,EAAE;AAChE,YAAA,MAAM,EAAE,KAAK;AACb,YAAA,OAAO,EAAE,IAAI;AACd,SAAA,CAAC,CAAC;IACL;;AAIA;;;;;;AAMG;AACH,IAAA,IAAI,CAAC,IAAY,EAAA;AACf,QAAA,IAAI,CAAC,IAAI,CAAC,oBAAoB,EAAE,EAAE;YAChC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACxC;QACF;;;;;;;;QAQA,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI;YAAE;QAC1C,IAAI,CAAC,aAAa,EAAE;AACpB,QAAA,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;;;;;;AAMpB,QAAA,MAAM,GAAG,GAAG,EAAE,IAAI,CAAC,IAAI;AACvB,QAAA,MAAM,OAAO,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC;AAC1C,QAAA,IAAI,CAAC,UAAU,GAAG,CAAC,KAAmB,KAAU;AAC9C,YAAA,IAAI,GAAG,KAAK,IAAI,CAAC,IAAI;gBAAE;AACvB,YAAA,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC;AAC9B,QAAA,CAAC;;;AAGD,QAAA,IAAI,CAAC,eAAe,GAAG,MAAW;AAChC,YAAA,IAAI,GAAG,KAAK,IAAI,CAAC,IAAI;gBAAE;YACvB,IAAI,CAAC,SAAS,CAAC;AACb,gBAAA,IAAI,EAAE,WAAW;AACjB,gBAAA,OAAO,EAAE,0DAA0D;AACpE,aAAA,CAAC;AACJ,QAAA,CAAC;QACD,OAAO,CAAC,gBAAgB,CAAC,SAAS,EAAE,IAAI,CAAC,UAAU,CAAC;QACpD,OAAO,CAAC,gBAAgB,CAAC,cAAc,EAAE,IAAI,CAAC,eAAe,CAAC;AAC9D,QAAA,IAAI,CAAC,QAAQ,GAAG,OAAO;AACvB,QAAA,IAAI,CAAC,KAAK,GAAG,IAAI;IACnB;AAEA;;;;;AAKG;AACH,IAAA,IAAI,CAAC,IAAS,EAAA;AACZ,QAAA,IAAI,CAAC,IAAI,CAAC,oBAAoB,EAAE,EAAE;YAChC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACxC;QACF;AACA,QAAA,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE;YAClB,IAAI,CAAC,SAAS,CAAC;AACb,gBAAA,IAAI,EAAE,mBAAmB;AACzB,gBAAA,OAAO,EAAE,qDAAqD;AAC/D,aAAA,CAAC;YACF;QACF;AACA,QAAA,IAAI;AACF,YAAA,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC;QACjC;QAAE,OAAO,GAAG,EAAE;YACZ,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;QAC3C;IACF;;IAGA,KAAK,GAAA;QACH,IAAI,CAAC,aAAa,EAAE;IACtB;AAEA;;;;;;;;;;;AAWG;IACH,OAAO,GAAA;;;;QAIL,IAAI,CAAC,IAAI,EAAE;QACX,IAAI,CAAC,aAAa,EAAE;AACpB,QAAA,IAAI,CAAC,MAAM,GAAG,IAAI;IACpB;;;;;IAOQ,UAAU,GAA2C,IAAI;IACzD,eAAe,GAAwB,IAAI;IAE3C,aAAa,GAAA;QACnB,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE;QACpB,IAAI,CAAC,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,IAAI,CAAC,UAAW,CAAC;QAC9D,IAAI,CAAC,QAAQ,CAAC,mBAAmB,CAAC,cAAc,EAAE,IAAI,CAAC,eAAgB,CAAC;AACxE,QAAA,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE;AACrB,QAAA,IAAI,CAAC,QAAQ,GAAG,IAAI;AACpB,QAAA,IAAI,CAAC,KAAK,GAAG,IAAI;AACjB,QAAA,IAAI,CAAC,UAAU,GAAG,IAAI;AACtB,QAAA,IAAI,CAAC,eAAe,GAAG,IAAI;IAC7B;IAEQ,oBAAoB,GAAA;AAC1B,QAAA,OAAO,OAAO,gBAAgB,KAAK,WAAW;IAChD;AAEQ,IAAA,eAAe,CAAC,GAAY,EAAA;AAClC,QAAA,IAAI,GAAG,YAAY,KAAK,EAAE;;;AAGxB,YAAA,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE;QACjD;AACA,QAAA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE;IAChD;IAEQ,iBAAiB,GAAA;QACvB,OAAO;AACL,YAAA,IAAI,EAAE,mBAAmB;AACzB,YAAA,OAAO,EAAE,wDAAwD;SAClE;IACH;;;ACpPF,IAAI,UAAU,GAAG,KAAK;AAEtB;AACA;AACA;AACA;AACA,MAAM,cAAc,GAAG,qBAAqB;AAC5C,MAAM,cAAc,GAAG,qBAAqB;AAE5C,SAAS,WAAW,CAAC,cAAuB,EAAA;;;;;;AAM1C,IAAA,IAAI,cAAc,CAAC,YAAY,CAAC,cAAc,CAAC,EAAE;QAC/C,OAAO,cAAc,CAAC,YAAY,CAAC,cAAc,CAAC,IAAI,EAAE;IAC1D;IACA,MAAM,QAAQ,GAAG,cAAc,CAAC,YAAY,CAAC,cAAc,CAAC;AAC5D,IAAA,IAAI,CAAC,QAAQ;AAAE,QAAA,OAAO,IAAI;;;;;;AAM1B,IAAA,IAAI,MAAsB;AAC1B,IAAA,IAAI;AACF,QAAA,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC;IAC3C;AAAE,IAAA,MAAM;AACN,QAAA,OAAO,IAAI;IACb;AACA,IAAA,IAAI,CAAC,MAAM;AAAE,QAAA,OAAO,IAAI;;;;;;IAMxB,IACE,MAAM,YAAY,gBAAgB;AAClC,QAAA,MAAM,YAAY,mBAAmB;QACrC,MAAM,YAAY,iBAAiB,EACnC;QACA,OAAO,MAAM,CAAC,KAAK;IACrB;;;;;;AAMA,IAAA,OAAO,MAAM,CAAC,WAAW,IAAI,EAAE;AACjC;AAEA,SAAS,WAAW,CAAC,KAAY,EAAA;AAC/B,IAAA,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM;AAC3B,IAAA,IAAI,EAAE,MAAM,YAAY,OAAO,CAAC;QAAE;AAElC,IAAA,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,CAAU,CAAA,CAAA,EAAI,MAAM,CAAC,gBAAgB,CAAA,CAAA,CAAG,CAAC;AAC9E,IAAA,IAAI,CAAC,cAAc;QAAE;IAErB,MAAM,WAAW,GAAG,cAAc,CAAC,YAAY,CAAC,MAAM,CAAC,gBAAgB,CAAC;AACxE,IAAA,IAAI,CAAC,WAAW;QAAE;;;;;AAMlB,IAAA,MAAM,aAAa,GAAG,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;IACnE,MAAM,gBAAgB,GAAG,QAAQ,CAAC,cAAc,CAAC,WAAW,CAAC;IAC7D,IAAI,CAAC,aAAa,IAAI,EAAE,gBAAgB,YAAY,aAAa,CAAC;QAAE;AAEpE,IAAA,MAAM,IAAI,GAAG,WAAW,CAAC,cAAc,CAAC;;;IAGxC,IAAI,IAAI,KAAK,IAAI;QAAE;;;;IAKnB,KAAK,CAAC,cAAc,EAAE;AACrB,IAAA,gBAAiC,CAAC,IAAI,CAAC,IAAI,CAAC;AAC/C;SAEgB,mBAAmB,GAAA;AACjC,IAAA,IAAI,UAAU;QAAE;IAChB,UAAU,GAAG,IAAI;AACjB,IAAA,QAAQ,CAAC,gBAAgB,CAAC,OAAO,EAAE,WAAW,CAAC;AACjD;;ACpFA;AACA;AACM,MAAO,YAAa,SAAQ,WAAW,CAAA;;;;AAI3C,IAAA,OAAO,2BAA2B,GAAG,IAAI;IACzC,OAAO,UAAU,GAAgB;QAC/B,GAAG,aAAa,CAAC,UAAU;;;;;;;AAO3B,QAAA,MAAM,EAAE;AACN,YAAA,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE;AACnC,YAAA,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE;AACxC,SAAA;;;AAGD,QAAA,QAAQ,EAAE,aAAa,CAAC,UAAU,CAAC,QAAQ;KAC5C;IACD,WAAW,kBAAkB,GAAA,EAAe,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;AAErD,IAAA,KAAK;AACL,IAAA,yBAAyB,GAAkB,OAAO,CAAC,OAAO,EAAE;AAEpE,IAAA,WAAA,GAAA;AACE,QAAA,KAAK,EAAE;QACP,IAAI,CAAC,KAAK,GAAG,IAAI,aAAa,CAAC,IAAI,CAAC;IACtC;AAEA,IAAA,IAAI,wBAAwB,GAAA;QAC1B,OAAO,IAAI,CAAC,yBAAyB;IACvC;;AAIA,IAAA,IAAI,IAAI,GAAA;QACN,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE;IACxC;IAEA,IAAI,IAAI,CAAC,KAAa,EAAA;AACpB,QAAA,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC;IAClC;AAEA,IAAA,IAAI,MAAM,GAAA;AACR,QAAA,OAAO,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC;IACpC;IAEA,IAAI,MAAM,CAAC,KAAc,EAAA;QACvB,IAAI,KAAK,EAAE;AACT,YAAA,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,EAAE,CAAC;QACjC;aAAO;AACL,YAAA,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC;QAChC;IACF;;AAIA,IAAA,IAAI,OAAO,GAAA;AACT,QAAA,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO;IAC3B;AAEA,IAAA,IAAI,KAAK,GAAA;AACP,QAAA,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK;IACzB;;IAIA,IAAI,GAAA;AACF,QAAA,IAAI,IAAI,CAAC,IAAI,EAAE;YACb,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;QAC5B;IACF;AAEA,IAAA,IAAI,CAAC,IAAS,EAAA;AACZ,QAAA,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;IACvB;IAEA,KAAK,GAAA;AACH,QAAA,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE;IACpB;;AAIA,IAAA,wBAAwB,CAAC,IAAY,EAAE,SAAwB,EAAE,QAAuB,EAAA;AACtF,QAAA,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,WAAW,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,QAAQ,EAAE;AACnE,YAAA,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC;QAC3B;IACF;IAEA,iBAAiB,GAAA;AACf,QAAA,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM;AAC3B,QAAA,IAAI,MAAM,CAAC,WAAW,EAAE;AACtB,YAAA,mBAAmB,EAAE;QACvB;QACA,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,EAAE;YAC7B,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;QAC5B;;;QAGA,IAAI,CAAC,yBAAyB,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE;IACvD;IAEA,oBAAoB,GAAA;;;;;;;;;;;AAWlB,QAAA,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE;IACtB;;;SCxHc,kBAAkB,GAAA;AAChC,IAAA,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE;QAClD,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAE,YAAY,CAAC;IAChE;AACF;;ACHM,SAAU,kBAAkB,CAAC,UAA4B,EAAA;IAC7D,IAAI,UAAU,EAAE;QACd,SAAS,CAAC,UAAU,CAAC;IACvB;AACA,IAAA,kBAAkB,EAAE;AACtB;;;;"}
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":["../src/config.ts","../src/core/BroadcastCore.ts","../src/autoTrigger.ts","../src/components/Broadcast.ts","../src/registerComponents.ts","../src/bootstrapBroadcast.ts"],"sourcesContent":["import { IConfig, IWritableConfig } from \"./types.js\";\n\ninterface IInternalConfig extends IConfig {\n autoTrigger: boolean;\n triggerAttribute: string;\n tagNames: {\n broadcast: string;\n };\n}\n\nconst _config: IInternalConfig = {\n autoTrigger: true,\n triggerAttribute: \"data-broadcast-target\",\n tagNames: {\n broadcast: \"wcs-broadcast\",\n },\n};\n\nfunction deepFreeze<T>(obj: T): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n Object.freeze(obj);\n for (const key of Object.keys(obj)) {\n deepFreeze((obj as Record<string, unknown>)[key]);\n }\n return obj;\n}\n\nfunction deepClone<T>(obj: T): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n const clone: Record<string, unknown> = {};\n for (const key of Object.keys(obj)) {\n clone[key] = deepClone((obj as Record<string, unknown>)[key]);\n }\n return clone as T;\n}\n\nlet frozenConfig: IConfig | null = null;\n\n// Live reference to the mutable internal config: reads always reflect the latest\n// setConfig() call. The readonly IConfig type only blocks callers from writing\n// through it — the underlying object still changes. If you need a stable,\n// frozen snapshot that won't move under you, use getConfig() instead.\nexport const config: IConfig = _config as IConfig;\n\nexport function getConfig(): IConfig {\n if (!frozenConfig) {\n frozenConfig = deepFreeze(deepClone(_config));\n }\n return frozenConfig;\n}\n\nexport function setConfig(partialConfig: IWritableConfig): void {\n if (typeof partialConfig.autoTrigger === \"boolean\") {\n _config.autoTrigger = partialConfig.autoTrigger;\n }\n if (typeof partialConfig.triggerAttribute === \"string\") {\n _config.triggerAttribute = partialConfig.triggerAttribute;\n }\n if (partialConfig.tagNames) {\n Object.assign(_config.tagNames, partialConfig.tagNames);\n }\n frozenConfig = null;\n}\n","import { IWcBindable, WcsBroadcastErrorDetail } from \"../types.js\";\n\n/**\n * Headless cross-tab messaging primitive. A thin, framework-agnostic wrapper\n * around the BroadcastChannel API exposed through the wc-bindable protocol.\n *\n * BroadcastChannel is a same-origin pub/sub bus: every context (tab, iframe,\n * worker) that opens a channel with the same `name` receives every other\n * context's posts — but NOT its own. This self-exclusion is the whole point:\n * `post` is a `state → element` action (command-token) and an incoming\n * `message` is an `element → state` notification (event-token), but the two\n * only close the loop *across* a context boundary. Within a single tab a lone\n * `<wcs-broadcast>` never hears itself; open the page in two tabs to see the\n * round-trip.\n *\n * Unlike WebSocketCore there is no connection state, no reconnect, and no wire\n * encoding: a channel is \"open\" the moment it is constructed, and payloads ride\n * the browser's structured clone (objects pass through as-is, no JSON\n * round-trip). The only failure surfaces are a non-cloneable `post`\n * (`DataCloneError`), a `messageerror` (a peer posted something this context\n * cannot deserialize), and an absent `BroadcastChannel` constructor\n * (`unsupported`). All three flow through the `error` property — the Core never\n * throws — symmetrical with FetchCore / ClipboardCore.\n */\nexport class BroadcastCore extends EventTarget {\n static wcBindable: IWcBindable = {\n protocol: \"wc-bindable\",\n version: 1,\n properties: [\n { name: \"message\", event: \"wcs-broadcast:message\" },\n { name: \"error\", event: \"wcs-broadcast:error\" },\n ],\n commands: [\n { name: \"open\" },\n { name: \"post\" },\n { name: \"close\" },\n ],\n };\n\n private _target: EventTarget;\n private _channel: BroadcastChannel | null = null;\n private _name: string | null = null;\n private _message: any = null;\n private _error: WcsBroadcastErrorDetail | null = null;\n // Generation guard (§3.4): bumped on dispose(). An incoming message /\n // messageerror that fires after the Shell disconnected (a peer posted between\n // disconnect and the channel actually closing, or a queued event drains late)\n // has a stale `gen` and MUST NOT write state to a torn-down element. A boolean\n // flag is insufficient: dispose→reconnect would let a stale event slip through.\n private _gen = 0;\n // SSR (§3.8): a channel opens synchronously (no asynchronous probe to await),\n // so readiness is immediate.\n private _ready: Promise<void> = Promise.resolve();\n\n constructor(target?: EventTarget) {\n super();\n this._target = target ?? this;\n }\n\n get ready(): Promise<void> {\n return this._ready;\n }\n\n get message(): any {\n return this._message;\n }\n\n get error(): WcsBroadcastErrorDetail | null {\n return this._error;\n }\n\n // --- Lifecycle (§3.5) ---\n\n // observe() establishes monitoring. BroadcastChannel is command-driven (the\n // Shell calls open(name) from connectedCallback / attributeChangedCallback),\n // so there is no subscription for observe() to set up here: it is an idempotent\n // no-op that resolves once ready. It exists for skeleton symmetry with the\n // monitor-style nodes so a host can uniformly await observe() == ready.\n observe(): Promise<void> {\n return this._ready;\n }\n\n // --- State setters with event dispatch ---\n\n // Deliberately NO same-value guard (unlike `error` below). A received message\n // is an event, not idempotent state: a peer posting the same value twice is\n // two distinct occurrences and must re-fire wcs-broadcast:message each time so\n // a `message:` binding and any `eventToken.message:` subscriber see both.\n private _setMessage(message: any): void {\n this._message = message;\n this._target.dispatchEvent(new CustomEvent(\"wcs-broadcast:message\", {\n detail: message,\n bubbles: true,\n }));\n }\n\n private _setError(error: WcsBroadcastErrorDetail | null): void {\n // Same-value guard. `error` has no derived state, so suppressing redundant\n // null→null dispatches (e.g. a successful open clearing an already-null\n // error) avoids spurious events. Reference identity is sufficient: each\n // failure builds a fresh object, and the clear path always passes null.\n if (this._error === error) return;\n this._error = error;\n this._target.dispatchEvent(new CustomEvent(\"wcs-broadcast:error\", {\n detail: error,\n bubbles: true,\n }));\n }\n\n // --- Public API ---\n\n /**\n * Join the named channel. Any previously-open channel is closed first, so\n * calling `open()` again switches channels. When the BroadcastChannel\n * constructor is unavailable this surfaces an `unsupported` error and leaves\n * the Core channel-less (a later `post()` then errors loudly rather than\n * silently dropping).\n */\n open(name: string): void {\n if (!this._hasBroadcastChannel()) {\n this._setError(this._unsupportedError());\n return;\n }\n // Idempotent on the same channel: re-opening the channel we are already on\n // is pure churn (BroadcastChannel has no reconnect semantics). This also\n // absorbs the custom-element *upgrade* path — when a connected element with\n // a `name` attribute is upgraded (autoloader defines the tag after the\n // markup exists), the spec fires attributeChangedCallback (isConnected ===\n // true) *and* connectedCallback, so the Shell calls open() twice. Without\n // this guard that would create a channel and immediately tear it down.\n if (this._channel && this._name === name) return;\n this._closeChannel();\n this._setError(null);\n // Capture the generation for this channel (§3.4). The listeners below close\n // over `gen`; an event that fires after dispose() (which bumps _gen) is\n // recognised as stale and dropped without writing state to a torn-down\n // element. The handlers are stored so _closeChannel() can remove them by the\n // same reference.\n const gen = ++this._gen;\n const channel = new BroadcastChannel(name);\n this._onMessage = (event: MessageEvent): void => {\n if (gen !== this._gen) return;\n this._setMessage(event.data);\n };\n // Fired when a peer posted a value this context cannot deserialize. The event\n // carries no usable payload, so report a synthetic DataError.\n this._onMessageError = (): void => {\n if (gen !== this._gen) return;\n this._setError({\n name: \"DataError\",\n message: \"Failed to deserialize a message received on the channel.\",\n });\n };\n channel.addEventListener(\"message\", this._onMessage);\n channel.addEventListener(\"messageerror\", this._onMessageError);\n this._channel = channel;\n this._name = name;\n }\n\n /**\n * Post a structured-cloneable value to every other context on the channel.\n * The local context never receives it (self-exclusion). Never throws:\n * a non-cloneable value surfaces as a `DataCloneError` through `error`, and\n * posting with no open channel surfaces an `InvalidStateError`.\n */\n post(data: any): void {\n if (!this._hasBroadcastChannel()) {\n this._setError(this._unsupportedError());\n return;\n }\n if (!this._channel) {\n this._setError({\n name: \"InvalidStateError\",\n message: \"Channel is not open. Call open(name) before post().\",\n });\n return;\n }\n try {\n this._channel.postMessage(data);\n } catch (err) {\n this._setError(this._normalizeError(err));\n }\n }\n\n /** Leave the channel. Idempotent — a no-op when no channel is open. */\n close(): void {\n this._closeChannel();\n }\n\n /**\n * Tear the Core down for a disconnected Shell: close the channel and reset the\n * error shadow silently (no dispatch on a torn-down element). A later\n * reconnect re-opens via the Shell's connectedCallback.\n *\n * Asymmetry by design: `_message` is deliberately NOT reset. `error` is\n * transient connection state — a stale error from a previous channel would be\n * misleading after a reconnect, so it is cleared. `message` is the last value\n * received (an event payload), not connection state; it is retained as the\n * Core's last-known datum so a binding still reads it across a disconnect/\n * reconnect, and it is naturally overwritten by the next incoming message.\n */\n dispose(): void {\n // Bump the generation first (§3.4) so any message/messageerror that drains\n // after teardown is recognised as stale, then close the channel and reset the\n // error shadow silently.\n this._gen++;\n this._closeChannel();\n this._error = null;\n }\n\n // --- Internal ---\n\n // Per-channel listeners, (re)created in open() so each closes over its own\n // generation (§3.4). null while no channel is open; the real handlers are\n // installed by open() and removed by the same reference in _closeChannel().\n private _onMessage: ((event: MessageEvent) => void) | null = null;\n private _onMessageError: (() => void) | null = null;\n\n private _closeChannel(): void {\n if (!this._channel) return;\n this._channel.removeEventListener(\"message\", this._onMessage!);\n this._channel.removeEventListener(\"messageerror\", this._onMessageError!);\n this._channel.close();\n this._channel = null;\n this._name = null;\n this._onMessage = null;\n this._onMessageError = null;\n }\n\n private _hasBroadcastChannel(): boolean {\n return typeof BroadcastChannel !== \"undefined\";\n }\n\n private _normalizeError(err: unknown): WcsBroadcastErrorDetail {\n if (err instanceof Error) {\n // DOMException is an Error subclass; its `name` (DataCloneError, etc.) is\n // the meaningful discriminator for consumers switching on failure kind.\n return { name: err.name, message: err.message };\n }\n return { name: \"Error\", message: String(err) };\n }\n\n private _unsupportedError(): WcsBroadcastErrorDetail {\n return {\n name: \"NotSupportedError\",\n message: \"BroadcastChannel is not available in this environment.\",\n };\n }\n}\n","import { config } from \"./config.js\";\nimport type { WcsBroadcast } from \"./components/Broadcast.js\";\n\nlet registered = false;\n\n// Attribute names for the optional post-on-click DOM trigger (clipboard.js-style\n// DX). The element carrying `data-broadcast-target` points at a <wcs-broadcast>\n// by id; the text to post comes from either a literal `data-broadcast-text` or\n// a `data-broadcast-from` CSS selector resolving to a source element.\nconst TEXT_ATTRIBUTE = \"data-broadcast-text\";\nconst FROM_ATTRIBUTE = \"data-broadcast-from\";\n\nfunction resolveText(triggerElement: Element): string | null {\n // Literal text wins when present (including an empty string — posting \"\" is a\n // legitimate request). The `?? \"\"` right-hand side is defensive and\n // unreachable: hasAttribute() just returned true, so getAttribute() cannot be\n // null here. It exists only to satisfy the `string | null` return type — do\n // not chase coverage on it (the DOM contract makes the null branch impossible).\n if (triggerElement.hasAttribute(TEXT_ATTRIBUTE)) {\n return triggerElement.getAttribute(TEXT_ATTRIBUTE) ?? \"\";\n }\n const selector = triggerElement.getAttribute(FROM_ATTRIBUTE);\n if (!selector) return null;\n // A user-authored selector can be syntactically invalid (e.g. `[data-*` or a\n // bare `:not()`), which makes querySelector throw a SyntaxError. Swallow it\n // and treat the source as unresolvable — the same \"nothing to post\" path as a\n // selector that matches no element — so one bad attribute never crashes the\n // document-level click handler and kills autoTrigger for the whole tab.\n let source: Element | null;\n try {\n source = document.querySelector(selector);\n } catch {\n return null;\n }\n if (!source) return null;\n // Read a form control's `value`; fall back to text content. A bare\n // `\"value\" in source` check is too broad — it also matches <button>,\n // <li value>, <progress>, etc. (which carry an unrelated `value`), posting the\n // wrong thing. Narrow to the text-bearing controls a user actually points\n // `data-broadcast-from` at; everything else falls through to textContent.\n if (\n source instanceof HTMLInputElement ||\n source instanceof HTMLTextAreaElement ||\n source instanceof HTMLSelectElement\n ) {\n return source.value;\n }\n // `?? \"\"` is defensive: per the DOM spec only Document / DocumentType /\n // Notation nodes have a null `textContent`, and querySelector only ever\n // returns an Element (whose textContent is always a string). The branch is\n // therefore unreachable in practice and kept solely for the `string | null`\n // type — not worth a contrived test.\n return source.textContent ?? \"\";\n}\n\nfunction handleClick(event: Event): void {\n const target = event.target;\n if (!(target instanceof Element)) return;\n\n const triggerElement = target.closest<Element>(`[${config.triggerAttribute}]`);\n if (!triggerElement) return;\n\n const broadcastId = triggerElement.getAttribute(config.triggerAttribute);\n if (!broadcastId) return;\n\n // Resolve the registered constructor at call time instead of importing\n // WcsBroadcast as a value (avoids a components ⇄ autoTrigger import cycle:\n // Broadcast.connectedCallback() calls registerAutoTrigger()). instanceof\n // against the customElements registry keeps the same identity guarantee.\n const BroadcastCtor = customElements.get(config.tagNames.broadcast);\n const broadcastElement = document.getElementById(broadcastId);\n if (!BroadcastCtor || !(broadcastElement instanceof BroadcastCtor)) return;\n\n const text = resolveText(triggerElement);\n // No resolvable source: leave the click alone (do not preventDefault) so the\n // element's default action is unaffected.\n if (text === null) return;\n\n // Suppress the default action so a post can run without navigating. Intentional:\n // do not attach data-broadcast-target to an element whose default action you\n // also want (a real <a href> link). See README \"Optional DOM Triggering\".\n event.preventDefault();\n (broadcastElement as WcsBroadcast).post(text);\n}\n\nexport function registerAutoTrigger(): void {\n if (registered) return;\n registered = true;\n document.addEventListener(\"click\", handleClick);\n}\n\nexport function unregisterAutoTrigger(): void {\n if (!registered) return;\n registered = false;\n document.removeEventListener(\"click\", handleClick);\n}\n","import { config } from \"../config.js\";\nimport { IWcBindable, WcsBroadcastErrorDetail } from \"../types.js\";\nimport { BroadcastCore } from \"../core/BroadcastCore.js\";\nimport { registerAutoTrigger } from \"../autoTrigger.js\";\n\n// Named WcsBroadcast (not `Broadcast`) to match the <wcs-clipboard> WcsClipboard\n// / <wcs-ws> WcsWebSocket convention and avoid shadowing any global.\nexport class WcsBroadcast extends HTMLElement {\n // SSR (§4.4): the channel opens synchronously in connectedCallback, so the\n // Core's observe() resolves immediately; we still expose connectedCallbackPromise\n // so a state binder can uniformly await readiness before snapshotting.\n static hasConnectedCallbackPromise = true;\n static wcBindable: IWcBindable = {\n ...BroadcastCore.wcBindable,\n // Shell-level settable surface. `name` selects the channel; `manual`\n // suppresses auto-open on connect. There is no momentary `post` property:\n // posting needs an argument (the payload), so element actions run via\n // command-token (`command.post: $command.ping`) or the DOM autoTrigger, not\n // a value-derived setter — keeping `post` a plain command keeps the\n // command-token wiring (`command.post:`) readable.\n inputs: [\n { name: \"name\", attribute: \"name\" },\n { name: \"manual\", attribute: \"manual\" },\n ],\n // Commands are identical to the Core's — no rename needed since the `name` /\n // `manual` attribute accessors do not collide with open/post/close.\n commands: BroadcastCore.wcBindable.commands,\n };\n static get observedAttributes(): string[] { return [\"name\"]; }\n\n private _core: BroadcastCore;\n private _connectedCallbackPromise: Promise<void> = Promise.resolve();\n private _internals: ElementInternals | null = null;\n\n constructor() {\n super();\n this._core = new BroadcastCore(this);\n this._internals = this._initInternals();\n this._wireStates({\n \"wcs-broadcast:error\": (d) => ({ error: d != null }),\n });\n }\n\n // CSS state reflection (:state()) — debug-only snapshot getter. NOT part of\n // wc-bindable (not a bind target); see README \"CSS styling with :state()\".\n // MUST NOT return the live CustomStateSet (that would let callers write\n // states from outside, defeating the point of :state() being read-only).\n get debugStates(): string[] {\n return this._internals ? [...this._internals.states] : [];\n }\n\n private _initInternals(): ElementInternals | null {\n // never-throw (async-io-node-guidelines.md §3.6): attachInternals is absent\n // in happy-dom / older environments, and pre-125 Chromium rejects\n // non-dashed state names from states.add() (probed and discarded here).\n // Either case silently disables reflection — the component still works,\n // it just doesn't expose :state() selectors.\n try {\n if (typeof this.attachInternals !== \"function\") return null;\n const internals = this.attachInternals();\n internals.states.add(\"wcs-probe\");\n internals.states.delete(\"wcs-probe\");\n return internals;\n } catch {\n return null;\n }\n }\n\n private _wireStates(map: Record<string, (detail: any) => Record<string, boolean>>): void {\n if (this._internals === null) return;\n const states = this._internals.states;\n for (const [event, toStates] of Object.entries(map)) {\n this.addEventListener(event, (e) => {\n const debug = this.hasAttribute(\"debug-states\");\n for (const [name, on] of Object.entries(toStates((e as CustomEvent).detail))) {\n try {\n if (on) { states.add(name); } else { states.delete(name); }\n } catch { /* never-throw */ }\n if (debug) this.toggleAttribute(`data-wcs-state-${name}`, on);\n }\n });\n }\n }\n\n get connectedCallbackPromise(): Promise<void> {\n return this._connectedCallbackPromise;\n }\n\n // --- Attribute accessors ---\n\n get name(): string {\n return this.getAttribute(\"name\") || \"\";\n }\n\n set name(value: string) {\n this.setAttribute(\"name\", value);\n }\n\n get manual(): boolean {\n return this.hasAttribute(\"manual\");\n }\n\n set manual(value: boolean) {\n if (value) {\n this.setAttribute(\"manual\", \"\");\n } else {\n this.removeAttribute(\"manual\");\n }\n }\n\n // --- Core delegated getters ---\n\n get message(): any {\n return this._core.message;\n }\n\n get error(): WcsBroadcastErrorDetail | null {\n return this._core.error;\n }\n\n // --- Commands ---\n\n open(): void {\n if (this.name) {\n this._core.open(this.name);\n }\n }\n\n post(data: any): void {\n this._core.post(data);\n }\n\n close(): void {\n this._core.close();\n }\n\n // --- Lifecycle ---\n\n attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null): void {\n if (name === \"name\" && this.isConnected && !this.manual && newValue) {\n this._core.open(newValue);\n }\n }\n\n connectedCallback(): void {\n this.style.display = \"none\";\n if (config.autoTrigger) {\n registerAutoTrigger();\n }\n if (!this.manual && this.name) {\n this._core.open(this.name);\n }\n // SSR (§4.4): expose the Core's readiness as connectedCallbackPromise. The\n // channel opens synchronously above, so observe() resolves immediately.\n this._connectedCallbackPromise = this._core.observe();\n }\n\n disconnectedCallback(): void {\n // Deliberately does NOT call unregisterAutoTrigger(). The autoTrigger click\n // listener is a single process-wide document listener (registerAutoTrigger\n // is idempotent), shared by every <wcs-broadcast> on the page — not owned by\n // this element. Tearing it down when the last element disconnects would\n // break a later-inserted trigger, so it is intentionally left installed for\n // the document's lifetime (one passive listener, negligible cost). This\n // mirrors <wcs-clipboard>, which registers but never unregisters either.\n // unregisterAutoTrigger stays exported purely as a symmetric teardown hook\n // for tests / advanced manual control; the production lifecycle never calls\n // it.\n this._core.dispose();\n }\n}\n","import { WcsBroadcast } from \"./components/Broadcast.js\";\nimport { config } from \"./config.js\";\n\nexport function registerComponents(): void {\n if (!customElements.get(config.tagNames.broadcast)) {\n customElements.define(config.tagNames.broadcast, WcsBroadcast);\n }\n}\n","import { setConfig } from \"./config.js\";\nimport { registerComponents } from \"./registerComponents.js\";\nimport { IWritableConfig } from \"./types.js\";\n\nexport function bootstrapBroadcast(userConfig?: IWritableConfig): void {\n if (userConfig) {\n setConfig(userConfig);\n }\n registerComponents();\n}\n"],"names":[],"mappings":"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"}
|
package/dist/index.esm.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e={autoTrigger:!0,triggerAttribute:"data-broadcast-target",tagNames:{broadcast:"wcs-broadcast"}};function t(e){if(null===e||"object"!=typeof e)return e;Object.freeze(e);for(const s of Object.keys(e))t(e[s]);return e}function s(e){if(null===e||"object"!=typeof e)return e;const t={};for(const r of Object.keys(e))t[r]=s(e[r]);return t}let r=null;const n=e;function a(){return r||(r=t(s(e))),r}class o extends EventTarget{static wcBindable={protocol:"wc-bindable",version:1,properties:[{name:"message",event:"wcs-broadcast:message"},{name:"error",event:"wcs-broadcast:error"}],commands:[{name:"open"},{name:"post"},{name:"close"}]};_target;_channel=null;_name=null;_message=null;_error=null;_gen=0;_ready=Promise.resolve();constructor(e){super(),this._target=e??this}get ready(){return this._ready}get message(){return this._message}get error(){return this._error}observe(){return this._ready}_setMessage(e){this._message=e,this._target.dispatchEvent(new CustomEvent("wcs-broadcast:message",{detail:e,bubbles:!0}))}_setError(e){this._error!==e&&(this._error=e,this._target.dispatchEvent(new CustomEvent("wcs-broadcast:error",{detail:e,bubbles:!0})))}open(e){if(!this._hasBroadcastChannel())return void this._setError(this._unsupportedError());if(this._channel&&this._name===e)return;this._closeChannel(),this._setError(null);const t=++this._gen,s=new BroadcastChannel(e);this._onMessage=e=>{t===this._gen&&this._setMessage(e.data)},this._onMessageError=()=>{t===this._gen&&this._setError({name:"DataError",message:"Failed to deserialize a message received on the channel."})},s.addEventListener("message",this._onMessage),s.addEventListener("messageerror",this._onMessageError),this._channel=s,this._name=e}post(e){if(this._hasBroadcastChannel())if(this._channel)try{this._channel.postMessage(e)}catch(e){this._setError(this._normalizeError(e))}else this._setError({name:"InvalidStateError",message:"Channel is not open. Call open(name) before post()."});else this._setError(this._unsupportedError())}close(){this._closeChannel()}dispose(){this._gen++,this._closeChannel(),this._error=null}_onMessage=null;_onMessageError=null;_closeChannel(){this._channel&&(this._channel.removeEventListener("message",this._onMessage),this._channel.removeEventListener("messageerror",this._onMessageError),this._channel.close(),this._channel=null,this._name=null,this._onMessage=null,this._onMessageError=null)}_hasBroadcastChannel(){return"undefined"!=typeof BroadcastChannel}_normalizeError(e){return e instanceof Error?{name:e.name,message:e.message}:{name:"Error",message:String(e)}}_unsupportedError(){return{name:"NotSupportedError",message:"BroadcastChannel is not available in this environment."}}}let i=!1;const
|
|
1
|
+
const e={autoTrigger:!0,triggerAttribute:"data-broadcast-target",tagNames:{broadcast:"wcs-broadcast"}};function t(e){if(null===e||"object"!=typeof e)return e;Object.freeze(e);for(const s of Object.keys(e))t(e[s]);return e}function s(e){if(null===e||"object"!=typeof e)return e;const t={};for(const r of Object.keys(e))t[r]=s(e[r]);return t}let r=null;const n=e;function a(){return r||(r=t(s(e))),r}class o extends EventTarget{static wcBindable={protocol:"wc-bindable",version:1,properties:[{name:"message",event:"wcs-broadcast:message"},{name:"error",event:"wcs-broadcast:error"}],commands:[{name:"open"},{name:"post"},{name:"close"}]};_target;_channel=null;_name=null;_message=null;_error=null;_gen=0;_ready=Promise.resolve();constructor(e){super(),this._target=e??this}get ready(){return this._ready}get message(){return this._message}get error(){return this._error}observe(){return this._ready}_setMessage(e){this._message=e,this._target.dispatchEvent(new CustomEvent("wcs-broadcast:message",{detail:e,bubbles:!0}))}_setError(e){this._error!==e&&(this._error=e,this._target.dispatchEvent(new CustomEvent("wcs-broadcast:error",{detail:e,bubbles:!0})))}open(e){if(!this._hasBroadcastChannel())return void this._setError(this._unsupportedError());if(this._channel&&this._name===e)return;this._closeChannel(),this._setError(null);const t=++this._gen,s=new BroadcastChannel(e);this._onMessage=e=>{t===this._gen&&this._setMessage(e.data)},this._onMessageError=()=>{t===this._gen&&this._setError({name:"DataError",message:"Failed to deserialize a message received on the channel."})},s.addEventListener("message",this._onMessage),s.addEventListener("messageerror",this._onMessageError),this._channel=s,this._name=e}post(e){if(this._hasBroadcastChannel())if(this._channel)try{this._channel.postMessage(e)}catch(e){this._setError(this._normalizeError(e))}else this._setError({name:"InvalidStateError",message:"Channel is not open. Call open(name) before post()."});else this._setError(this._unsupportedError())}close(){this._closeChannel()}dispose(){this._gen++,this._closeChannel(),this._error=null}_onMessage=null;_onMessageError=null;_closeChannel(){this._channel&&(this._channel.removeEventListener("message",this._onMessage),this._channel.removeEventListener("messageerror",this._onMessageError),this._channel.close(),this._channel=null,this._name=null,this._onMessage=null,this._onMessageError=null)}_hasBroadcastChannel(){return"undefined"!=typeof BroadcastChannel}_normalizeError(e){return e instanceof Error?{name:e.name,message:e.message}:{name:"Error",message:String(e)}}_unsupportedError(){return{name:"NotSupportedError",message:"BroadcastChannel is not available in this environment."}}}let i=!1;const l="data-broadcast-text";function c(e){const t=e.target;if(!(t instanceof Element))return;const s=t.closest(`[${n.triggerAttribute}]`);if(!s)return;const r=s.getAttribute(n.triggerAttribute);if(!r)return;const a=customElements.get(n.tagNames.broadcast),o=document.getElementById(r);if(!(a&&o instanceof a))return;const i=function(e){if(e.hasAttribute(l))return e.getAttribute(l)??"";const t=e.getAttribute("data-broadcast-from");if(!t)return null;let s;try{s=document.querySelector(t)}catch{return null}return s?s instanceof HTMLInputElement||s instanceof HTMLTextAreaElement||s instanceof HTMLSelectElement?s.value:s.textContent??"":null}(s);null!==i&&(e.preventDefault(),o.post(i))}class h extends HTMLElement{static hasConnectedCallbackPromise=!0;static wcBindable={...o.wcBindable,inputs:[{name:"name",attribute:"name"},{name:"manual",attribute:"manual"}],commands:o.wcBindable.commands};static get observedAttributes(){return["name"]}_core;_connectedCallbackPromise=Promise.resolve();_internals=null;constructor(){super(),this._core=new o(this),this._internals=this._initInternals(),this._wireStates({"wcs-broadcast:error":e=>({error:null!=e})})}get debugStates(){return this._internals?[...this._internals.states]:[]}_initInternals(){try{if("function"!=typeof this.attachInternals)return null;const e=this.attachInternals();return e.states.add("wcs-probe"),e.states.delete("wcs-probe"),e}catch{return null}}_wireStates(e){if(null===this._internals)return;const t=this._internals.states;for(const[s,r]of Object.entries(e))this.addEventListener(s,e=>{const s=this.hasAttribute("debug-states");for(const[n,a]of Object.entries(r(e.detail))){try{a?t.add(n):t.delete(n)}catch{}s&&this.toggleAttribute(`data-wcs-state-${n}`,a)}})}get connectedCallbackPromise(){return this._connectedCallbackPromise}get name(){return this.getAttribute("name")||""}set name(e){this.setAttribute("name",e)}get manual(){return this.hasAttribute("manual")}set manual(e){e?this.setAttribute("manual",""):this.removeAttribute("manual")}get message(){return this._core.message}get error(){return this._core.error}open(){this.name&&this._core.open(this.name)}post(e){this._core.post(e)}close(){this._core.close()}attributeChangedCallback(e,t,s){"name"===e&&this.isConnected&&!this.manual&&s&&this._core.open(s)}connectedCallback(){this.style.display="none",n.autoTrigger&&(i||(i=!0,document.addEventListener("click",c))),!this.manual&&this.name&&this._core.open(this.name),this._connectedCallbackPromise=this._core.observe()}disconnectedCallback(){this._core.dispose()}}function u(t){var s;t&&("boolean"==typeof(s=t).autoTrigger&&(e.autoTrigger=s.autoTrigger),"string"==typeof s.triggerAttribute&&(e.triggerAttribute=s.triggerAttribute),s.tagNames&&Object.assign(e.tagNames,s.tagNames),r=null),customElements.get(n.tagNames.broadcast)||customElements.define(n.tagNames.broadcast,h)}export{o as BroadcastCore,h as WcsBroadcast,u as bootstrapBroadcast,a as getConfig};
|
|
2
2
|
//# sourceMappingURL=index.esm.min.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.esm.min.js","sources":["../src/config.ts","../src/core/BroadcastCore.ts","../src/autoTrigger.ts","../src/components/Broadcast.ts","../src/bootstrapBroadcast.ts","../src/registerComponents.ts"],"sourcesContent":["import { IConfig, IWritableConfig } from \"./types.js\";\n\ninterface IInternalConfig extends IConfig {\n autoTrigger: boolean;\n triggerAttribute: string;\n tagNames: {\n broadcast: string;\n };\n}\n\nconst _config: IInternalConfig = {\n autoTrigger: true,\n triggerAttribute: \"data-broadcast-target\",\n tagNames: {\n broadcast: \"wcs-broadcast\",\n },\n};\n\nfunction deepFreeze<T>(obj: T): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n Object.freeze(obj);\n for (const key of Object.keys(obj)) {\n deepFreeze((obj as Record<string, unknown>)[key]);\n }\n return obj;\n}\n\nfunction deepClone<T>(obj: T): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n const clone: Record<string, unknown> = {};\n for (const key of Object.keys(obj)) {\n clone[key] = deepClone((obj as Record<string, unknown>)[key]);\n }\n return clone as T;\n}\n\nlet frozenConfig: IConfig | null = null;\n\n// Live reference to the mutable internal config: reads always reflect the latest\n// setConfig() call. The readonly IConfig type only blocks callers from writing\n// through it — the underlying object still changes. If you need a stable,\n// frozen snapshot that won't move under you, use getConfig() instead.\nexport const config: IConfig = _config as IConfig;\n\nexport function getConfig(): IConfig {\n if (!frozenConfig) {\n frozenConfig = deepFreeze(deepClone(_config));\n }\n return frozenConfig;\n}\n\nexport function setConfig(partialConfig: IWritableConfig): void {\n if (typeof partialConfig.autoTrigger === \"boolean\") {\n _config.autoTrigger = partialConfig.autoTrigger;\n }\n if (typeof partialConfig.triggerAttribute === \"string\") {\n _config.triggerAttribute = partialConfig.triggerAttribute;\n }\n if (partialConfig.tagNames) {\n Object.assign(_config.tagNames, partialConfig.tagNames);\n }\n frozenConfig = null;\n}\n","import { IWcBindable, WcsBroadcastErrorDetail } from \"../types.js\";\n\n/**\n * Headless cross-tab messaging primitive. A thin, framework-agnostic wrapper\n * around the BroadcastChannel API exposed through the wc-bindable protocol.\n *\n * BroadcastChannel is a same-origin pub/sub bus: every context (tab, iframe,\n * worker) that opens a channel with the same `name` receives every other\n * context's posts — but NOT its own. This self-exclusion is the whole point:\n * `post` is a `state → element` action (command-token) and an incoming\n * `message` is an `element → state` notification (event-token), but the two\n * only close the loop *across* a context boundary. Within a single tab a lone\n * `<wcs-broadcast>` never hears itself; open the page in two tabs to see the\n * round-trip.\n *\n * Unlike WebSocketCore there is no connection state, no reconnect, and no wire\n * encoding: a channel is \"open\" the moment it is constructed, and payloads ride\n * the browser's structured clone (objects pass through as-is, no JSON\n * round-trip). The only failure surfaces are a non-cloneable `post`\n * (`DataCloneError`), a `messageerror` (a peer posted something this context\n * cannot deserialize), and an absent `BroadcastChannel` constructor\n * (`unsupported`). All three flow through the `error` property — the Core never\n * throws — symmetrical with FetchCore / ClipboardCore.\n */\nexport class BroadcastCore extends EventTarget {\n static wcBindable: IWcBindable = {\n protocol: \"wc-bindable\",\n version: 1,\n properties: [\n { name: \"message\", event: \"wcs-broadcast:message\" },\n { name: \"error\", event: \"wcs-broadcast:error\" },\n ],\n commands: [\n { name: \"open\" },\n { name: \"post\" },\n { name: \"close\" },\n ],\n };\n\n private _target: EventTarget;\n private _channel: BroadcastChannel | null = null;\n private _name: string | null = null;\n private _message: any = null;\n private _error: WcsBroadcastErrorDetail | null = null;\n // Generation guard (§3.4): bumped on dispose(). An incoming message /\n // messageerror that fires after the Shell disconnected (a peer posted between\n // disconnect and the channel actually closing, or a queued event drains late)\n // has a stale `gen` and MUST NOT write state to a torn-down element. A boolean\n // flag is insufficient: dispose→reconnect would let a stale event slip through.\n private _gen = 0;\n // SSR (§3.8): a channel opens synchronously (no asynchronous probe to await),\n // so readiness is immediate.\n private _ready: Promise<void> = Promise.resolve();\n\n constructor(target?: EventTarget) {\n super();\n this._target = target ?? this;\n }\n\n get ready(): Promise<void> {\n return this._ready;\n }\n\n get message(): any {\n return this._message;\n }\n\n get error(): WcsBroadcastErrorDetail | null {\n return this._error;\n }\n\n // --- Lifecycle (§3.5) ---\n\n // observe() establishes monitoring. BroadcastChannel is command-driven (the\n // Shell calls open(name) from connectedCallback / attributeChangedCallback),\n // so there is no subscription for observe() to set up here: it is an idempotent\n // no-op that resolves once ready. It exists for skeleton symmetry with the\n // monitor-style nodes so a host can uniformly await observe() == ready.\n observe(): Promise<void> {\n return this._ready;\n }\n\n // --- State setters with event dispatch ---\n\n // Deliberately NO same-value guard (unlike `error` below). A received message\n // is an event, not idempotent state: a peer posting the same value twice is\n // two distinct occurrences and must re-fire wcs-broadcast:message each time so\n // a `message:` binding and any `eventToken.message:` subscriber see both.\n private _setMessage(message: any): void {\n this._message = message;\n this._target.dispatchEvent(new CustomEvent(\"wcs-broadcast:message\", {\n detail: message,\n bubbles: true,\n }));\n }\n\n private _setError(error: WcsBroadcastErrorDetail | null): void {\n // Same-value guard. `error` has no derived state, so suppressing redundant\n // null→null dispatches (e.g. a successful open clearing an already-null\n // error) avoids spurious events. Reference identity is sufficient: each\n // failure builds a fresh object, and the clear path always passes null.\n if (this._error === error) return;\n this._error = error;\n this._target.dispatchEvent(new CustomEvent(\"wcs-broadcast:error\", {\n detail: error,\n bubbles: true,\n }));\n }\n\n // --- Public API ---\n\n /**\n * Join the named channel. Any previously-open channel is closed first, so\n * calling `open()` again switches channels. When the BroadcastChannel\n * constructor is unavailable this surfaces an `unsupported` error and leaves\n * the Core channel-less (a later `post()` then errors loudly rather than\n * silently dropping).\n */\n open(name: string): void {\n if (!this._hasBroadcastChannel()) {\n this._setError(this._unsupportedError());\n return;\n }\n // Idempotent on the same channel: re-opening the channel we are already on\n // is pure churn (BroadcastChannel has no reconnect semantics). This also\n // absorbs the custom-element *upgrade* path — when a connected element with\n // a `name` attribute is upgraded (autoloader defines the tag after the\n // markup exists), the spec fires attributeChangedCallback (isConnected ===\n // true) *and* connectedCallback, so the Shell calls open() twice. Without\n // this guard that would create a channel and immediately tear it down.\n if (this._channel && this._name === name) return;\n this._closeChannel();\n this._setError(null);\n // Capture the generation for this channel (§3.4). The listeners below close\n // over `gen`; an event that fires after dispose() (which bumps _gen) is\n // recognised as stale and dropped without writing state to a torn-down\n // element. The handlers are stored so _closeChannel() can remove them by the\n // same reference.\n const gen = ++this._gen;\n const channel = new BroadcastChannel(name);\n this._onMessage = (event: MessageEvent): void => {\n if (gen !== this._gen) return;\n this._setMessage(event.data);\n };\n // Fired when a peer posted a value this context cannot deserialize. The event\n // carries no usable payload, so report a synthetic DataError.\n this._onMessageError = (): void => {\n if (gen !== this._gen) return;\n this._setError({\n name: \"DataError\",\n message: \"Failed to deserialize a message received on the channel.\",\n });\n };\n channel.addEventListener(\"message\", this._onMessage);\n channel.addEventListener(\"messageerror\", this._onMessageError);\n this._channel = channel;\n this._name = name;\n }\n\n /**\n * Post a structured-cloneable value to every other context on the channel.\n * The local context never receives it (self-exclusion). Never throws:\n * a non-cloneable value surfaces as a `DataCloneError` through `error`, and\n * posting with no open channel surfaces an `InvalidStateError`.\n */\n post(data: any): void {\n if (!this._hasBroadcastChannel()) {\n this._setError(this._unsupportedError());\n return;\n }\n if (!this._channel) {\n this._setError({\n name: \"InvalidStateError\",\n message: \"Channel is not open. Call open(name) before post().\",\n });\n return;\n }\n try {\n this._channel.postMessage(data);\n } catch (err) {\n this._setError(this._normalizeError(err));\n }\n }\n\n /** Leave the channel. Idempotent — a no-op when no channel is open. */\n close(): void {\n this._closeChannel();\n }\n\n /**\n * Tear the Core down for a disconnected Shell: close the channel and reset the\n * error shadow silently (no dispatch on a torn-down element). A later\n * reconnect re-opens via the Shell's connectedCallback.\n *\n * Asymmetry by design: `_message` is deliberately NOT reset. `error` is\n * transient connection state — a stale error from a previous channel would be\n * misleading after a reconnect, so it is cleared. `message` is the last value\n * received (an event payload), not connection state; it is retained as the\n * Core's last-known datum so a binding still reads it across a disconnect/\n * reconnect, and it is naturally overwritten by the next incoming message.\n */\n dispose(): void {\n // Bump the generation first (§3.4) so any message/messageerror that drains\n // after teardown is recognised as stale, then close the channel and reset the\n // error shadow silently.\n this._gen++;\n this._closeChannel();\n this._error = null;\n }\n\n // --- Internal ---\n\n // Per-channel listeners, (re)created in open() so each closes over its own\n // generation (§3.4). null while no channel is open; the real handlers are\n // installed by open() and removed by the same reference in _closeChannel().\n private _onMessage: ((event: MessageEvent) => void) | null = null;\n private _onMessageError: (() => void) | null = null;\n\n private _closeChannel(): void {\n if (!this._channel) return;\n this._channel.removeEventListener(\"message\", this._onMessage!);\n this._channel.removeEventListener(\"messageerror\", this._onMessageError!);\n this._channel.close();\n this._channel = null;\n this._name = null;\n this._onMessage = null;\n this._onMessageError = null;\n }\n\n private _hasBroadcastChannel(): boolean {\n return typeof BroadcastChannel !== \"undefined\";\n }\n\n private _normalizeError(err: unknown): WcsBroadcastErrorDetail {\n if (err instanceof Error) {\n // DOMException is an Error subclass; its `name` (DataCloneError, etc.) is\n // the meaningful discriminator for consumers switching on failure kind.\n return { name: err.name, message: err.message };\n }\n return { name: \"Error\", message: String(err) };\n }\n\n private _unsupportedError(): WcsBroadcastErrorDetail {\n return {\n name: \"NotSupportedError\",\n message: \"BroadcastChannel is not available in this environment.\",\n };\n }\n}\n","import { config } from \"./config.js\";\nimport type { WcsBroadcast } from \"./components/Broadcast.js\";\n\nlet registered = false;\n\n// Attribute names for the optional post-on-click DOM trigger (clipboard.js-style\n// DX). The element carrying `data-broadcast-target` points at a <wcs-broadcast>\n// by id; the text to post comes from either a literal `data-broadcast-text` or\n// a `data-broadcast-from` CSS selector resolving to a source element.\nconst TEXT_ATTRIBUTE = \"data-broadcast-text\";\nconst FROM_ATTRIBUTE = \"data-broadcast-from\";\n\nfunction resolveText(triggerElement: Element): string | null {\n // Literal text wins when present (including an empty string — posting \"\" is a\n // legitimate request). The `?? \"\"` right-hand side is defensive and\n // unreachable: hasAttribute() just returned true, so getAttribute() cannot be\n // null here. It exists only to satisfy the `string | null` return type — do\n // not chase coverage on it (the DOM contract makes the null branch impossible).\n if (triggerElement.hasAttribute(TEXT_ATTRIBUTE)) {\n return triggerElement.getAttribute(TEXT_ATTRIBUTE) ?? \"\";\n }\n const selector = triggerElement.getAttribute(FROM_ATTRIBUTE);\n if (!selector) return null;\n // A user-authored selector can be syntactically invalid (e.g. `[data-*` or a\n // bare `:not()`), which makes querySelector throw a SyntaxError. Swallow it\n // and treat the source as unresolvable — the same \"nothing to post\" path as a\n // selector that matches no element — so one bad attribute never crashes the\n // document-level click handler and kills autoTrigger for the whole tab.\n let source: Element | null;\n try {\n source = document.querySelector(selector);\n } catch {\n return null;\n }\n if (!source) return null;\n // Read a form control's `value`; fall back to text content. A bare\n // `\"value\" in source` check is too broad — it also matches <button>,\n // <li value>, <progress>, etc. (which carry an unrelated `value`), posting the\n // wrong thing. Narrow to the text-bearing controls a user actually points\n // `data-broadcast-from` at; everything else falls through to textContent.\n if (\n source instanceof HTMLInputElement ||\n source instanceof HTMLTextAreaElement ||\n source instanceof HTMLSelectElement\n ) {\n return source.value;\n }\n // `?? \"\"` is defensive: per the DOM spec only Document / DocumentType /\n // Notation nodes have a null `textContent`, and querySelector only ever\n // returns an Element (whose textContent is always a string). The branch is\n // therefore unreachable in practice and kept solely for the `string | null`\n // type — not worth a contrived test.\n return source.textContent ?? \"\";\n}\n\nfunction handleClick(event: Event): void {\n const target = event.target;\n if (!(target instanceof Element)) return;\n\n const triggerElement = target.closest<Element>(`[${config.triggerAttribute}]`);\n if (!triggerElement) return;\n\n const broadcastId = triggerElement.getAttribute(config.triggerAttribute);\n if (!broadcastId) return;\n\n // Resolve the registered constructor at call time instead of importing\n // WcsBroadcast as a value (avoids a components ⇄ autoTrigger import cycle:\n // Broadcast.connectedCallback() calls registerAutoTrigger()). instanceof\n // against the customElements registry keeps the same identity guarantee.\n const BroadcastCtor = customElements.get(config.tagNames.broadcast);\n const broadcastElement = document.getElementById(broadcastId);\n if (!BroadcastCtor || !(broadcastElement instanceof BroadcastCtor)) return;\n\n const text = resolveText(triggerElement);\n // No resolvable source: leave the click alone (do not preventDefault) so the\n // element's default action is unaffected.\n if (text === null) return;\n\n // Suppress the default action so a post can run without navigating. Intentional:\n // do not attach data-broadcast-target to an element whose default action you\n // also want (a real <a href> link). See README \"Optional DOM Triggering\".\n event.preventDefault();\n (broadcastElement as WcsBroadcast).post(text);\n}\n\nexport function registerAutoTrigger(): void {\n if (registered) return;\n registered = true;\n document.addEventListener(\"click\", handleClick);\n}\n\nexport function unregisterAutoTrigger(): void {\n if (!registered) return;\n registered = false;\n document.removeEventListener(\"click\", handleClick);\n}\n","import { config } from \"../config.js\";\nimport { IWcBindable, WcsBroadcastErrorDetail } from \"../types.js\";\nimport { BroadcastCore } from \"../core/BroadcastCore.js\";\nimport { registerAutoTrigger } from \"../autoTrigger.js\";\n\n// Named WcsBroadcast (not `Broadcast`) to match the <wcs-clipboard> WcsClipboard\n// / <wcs-ws> WcsWebSocket convention and avoid shadowing any global.\nexport class WcsBroadcast extends HTMLElement {\n // SSR (§4.4): the channel opens synchronously in connectedCallback, so the\n // Core's observe() resolves immediately; we still expose connectedCallbackPromise\n // so a state binder can uniformly await readiness before snapshotting.\n static hasConnectedCallbackPromise = true;\n static wcBindable: IWcBindable = {\n ...BroadcastCore.wcBindable,\n // Shell-level settable surface. `name` selects the channel; `manual`\n // suppresses auto-open on connect. There is no momentary `post` property:\n // posting needs an argument (the payload), so element actions run via\n // command-token (`command.post: $command.ping`) or the DOM autoTrigger, not\n // a value-derived setter — keeping `post` a plain command keeps the\n // command-token wiring (`command.post:`) readable.\n inputs: [\n { name: \"name\", attribute: \"name\" },\n { name: \"manual\", attribute: \"manual\" },\n ],\n // Commands are identical to the Core's — no rename needed since the `name` /\n // `manual` attribute accessors do not collide with open/post/close.\n commands: BroadcastCore.wcBindable.commands,\n };\n static get observedAttributes(): string[] { return [\"name\"]; }\n\n private _core: BroadcastCore;\n private _connectedCallbackPromise: Promise<void> = Promise.resolve();\n\n constructor() {\n super();\n this._core = new BroadcastCore(this);\n }\n\n get connectedCallbackPromise(): Promise<void> {\n return this._connectedCallbackPromise;\n }\n\n // --- Attribute accessors ---\n\n get name(): string {\n return this.getAttribute(\"name\") || \"\";\n }\n\n set name(value: string) {\n this.setAttribute(\"name\", value);\n }\n\n get manual(): boolean {\n return this.hasAttribute(\"manual\");\n }\n\n set manual(value: boolean) {\n if (value) {\n this.setAttribute(\"manual\", \"\");\n } else {\n this.removeAttribute(\"manual\");\n }\n }\n\n // --- Core delegated getters ---\n\n get message(): any {\n return this._core.message;\n }\n\n get error(): WcsBroadcastErrorDetail | null {\n return this._core.error;\n }\n\n // --- Commands ---\n\n open(): void {\n if (this.name) {\n this._core.open(this.name);\n }\n }\n\n post(data: any): void {\n this._core.post(data);\n }\n\n close(): void {\n this._core.close();\n }\n\n // --- Lifecycle ---\n\n attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null): void {\n if (name === \"name\" && this.isConnected && !this.manual && newValue) {\n this._core.open(newValue);\n }\n }\n\n connectedCallback(): void {\n this.style.display = \"none\";\n if (config.autoTrigger) {\n registerAutoTrigger();\n }\n if (!this.manual && this.name) {\n this._core.open(this.name);\n }\n // SSR (§4.4): expose the Core's readiness as connectedCallbackPromise. The\n // channel opens synchronously above, so observe() resolves immediately.\n this._connectedCallbackPromise = this._core.observe();\n }\n\n disconnectedCallback(): void {\n // Deliberately does NOT call unregisterAutoTrigger(). The autoTrigger click\n // listener is a single process-wide document listener (registerAutoTrigger\n // is idempotent), shared by every <wcs-broadcast> on the page — not owned by\n // this element. Tearing it down when the last element disconnects would\n // break a later-inserted trigger, so it is intentionally left installed for\n // the document's lifetime (one passive listener, negligible cost). This\n // mirrors <wcs-clipboard>, which registers but never unregisters either.\n // unregisterAutoTrigger stays exported purely as a symmetric teardown hook\n // for tests / advanced manual control; the production lifecycle never calls\n // it.\n this._core.dispose();\n }\n}\n","import { setConfig } from \"./config.js\";\nimport { registerComponents } from \"./registerComponents.js\";\nimport { IWritableConfig } from \"./types.js\";\n\nexport function bootstrapBroadcast(userConfig?: IWritableConfig): void {\n if (userConfig) {\n setConfig(userConfig);\n }\n registerComponents();\n}\n","import { WcsBroadcast } from \"./components/Broadcast.js\";\nimport { config } from \"./config.js\";\n\nexport function registerComponents(): void {\n if (!customElements.get(config.tagNames.broadcast)) {\n customElements.define(config.tagNames.broadcast, WcsBroadcast);\n }\n}\n"],"names":["_config","autoTrigger","triggerAttribute","tagNames","broadcast","deepFreeze","obj","Object","freeze","key","keys","deepClone","clone","frozenConfig","config","getConfig","BroadcastCore","EventTarget","static","protocol","version","properties","name","event","commands","_target","_channel","_name","_message","_error","_gen","_ready","Promise","resolve","constructor","target","super","this","ready","message","error","observe","_setMessage","dispatchEvent","CustomEvent","detail","bubbles","_setError","open","_hasBroadcastChannel","_unsupportedError","_closeChannel","gen","channel","BroadcastChannel","_onMessage","data","_onMessageError","addEventListener","post","postMessage","err","_normalizeError","close","dispose","removeEventListener","Error","String","registered","TEXT_ATTRIBUTE","handleClick","Element","triggerElement","closest","broadcastId","getAttribute","BroadcastCtor","customElements","get","broadcastElement","document","getElementById","text","hasAttribute","selector","source","querySelector","HTMLInputElement","HTMLTextAreaElement","HTMLSelectElement","value","textContent","resolveText","preventDefault","WcsBroadcast","HTMLElement","wcBindable","inputs","attribute","observedAttributes","_core","_connectedCallbackPromise","connectedCallbackPromise","setAttribute","manual","removeAttribute","attributeChangedCallback","_oldValue","newValue","isConnected","connectedCallback","style","display","disconnectedCallback","bootstrapBroadcast","userConfig","partialConfig","assign","define"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.esm.min.js","sources":["../src/config.ts","../src/core/BroadcastCore.ts","../src/autoTrigger.ts","../src/components/Broadcast.ts","../src/bootstrapBroadcast.ts","../src/registerComponents.ts"],"sourcesContent":["import { IConfig, IWritableConfig } from \"./types.js\";\n\ninterface IInternalConfig extends IConfig {\n autoTrigger: boolean;\n triggerAttribute: string;\n tagNames: {\n broadcast: string;\n };\n}\n\nconst _config: IInternalConfig = {\n autoTrigger: true,\n triggerAttribute: \"data-broadcast-target\",\n tagNames: {\n broadcast: \"wcs-broadcast\",\n },\n};\n\nfunction deepFreeze<T>(obj: T): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n Object.freeze(obj);\n for (const key of Object.keys(obj)) {\n deepFreeze((obj as Record<string, unknown>)[key]);\n }\n return obj;\n}\n\nfunction deepClone<T>(obj: T): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n const clone: Record<string, unknown> = {};\n for (const key of Object.keys(obj)) {\n clone[key] = deepClone((obj as Record<string, unknown>)[key]);\n }\n return clone as T;\n}\n\nlet frozenConfig: IConfig | null = null;\n\n// Live reference to the mutable internal config: reads always reflect the latest\n// setConfig() call. The readonly IConfig type only blocks callers from writing\n// through it — the underlying object still changes. If you need a stable,\n// frozen snapshot that won't move under you, use getConfig() instead.\nexport const config: IConfig = _config as IConfig;\n\nexport function getConfig(): IConfig {\n if (!frozenConfig) {\n frozenConfig = deepFreeze(deepClone(_config));\n }\n return frozenConfig;\n}\n\nexport function setConfig(partialConfig: IWritableConfig): void {\n if (typeof partialConfig.autoTrigger === \"boolean\") {\n _config.autoTrigger = partialConfig.autoTrigger;\n }\n if (typeof partialConfig.triggerAttribute === \"string\") {\n _config.triggerAttribute = partialConfig.triggerAttribute;\n }\n if (partialConfig.tagNames) {\n Object.assign(_config.tagNames, partialConfig.tagNames);\n }\n frozenConfig = null;\n}\n","import { IWcBindable, WcsBroadcastErrorDetail } from \"../types.js\";\n\n/**\n * Headless cross-tab messaging primitive. A thin, framework-agnostic wrapper\n * around the BroadcastChannel API exposed through the wc-bindable protocol.\n *\n * BroadcastChannel is a same-origin pub/sub bus: every context (tab, iframe,\n * worker) that opens a channel with the same `name` receives every other\n * context's posts — but NOT its own. This self-exclusion is the whole point:\n * `post` is a `state → element` action (command-token) and an incoming\n * `message` is an `element → state` notification (event-token), but the two\n * only close the loop *across* a context boundary. Within a single tab a lone\n * `<wcs-broadcast>` never hears itself; open the page in two tabs to see the\n * round-trip.\n *\n * Unlike WebSocketCore there is no connection state, no reconnect, and no wire\n * encoding: a channel is \"open\" the moment it is constructed, and payloads ride\n * the browser's structured clone (objects pass through as-is, no JSON\n * round-trip). The only failure surfaces are a non-cloneable `post`\n * (`DataCloneError`), a `messageerror` (a peer posted something this context\n * cannot deserialize), and an absent `BroadcastChannel` constructor\n * (`unsupported`). All three flow through the `error` property — the Core never\n * throws — symmetrical with FetchCore / ClipboardCore.\n */\nexport class BroadcastCore extends EventTarget {\n static wcBindable: IWcBindable = {\n protocol: \"wc-bindable\",\n version: 1,\n properties: [\n { name: \"message\", event: \"wcs-broadcast:message\" },\n { name: \"error\", event: \"wcs-broadcast:error\" },\n ],\n commands: [\n { name: \"open\" },\n { name: \"post\" },\n { name: \"close\" },\n ],\n };\n\n private _target: EventTarget;\n private _channel: BroadcastChannel | null = null;\n private _name: string | null = null;\n private _message: any = null;\n private _error: WcsBroadcastErrorDetail | null = null;\n // Generation guard (§3.4): bumped on dispose(). An incoming message /\n // messageerror that fires after the Shell disconnected (a peer posted between\n // disconnect and the channel actually closing, or a queued event drains late)\n // has a stale `gen` and MUST NOT write state to a torn-down element. A boolean\n // flag is insufficient: dispose→reconnect would let a stale event slip through.\n private _gen = 0;\n // SSR (§3.8): a channel opens synchronously (no asynchronous probe to await),\n // so readiness is immediate.\n private _ready: Promise<void> = Promise.resolve();\n\n constructor(target?: EventTarget) {\n super();\n this._target = target ?? this;\n }\n\n get ready(): Promise<void> {\n return this._ready;\n }\n\n get message(): any {\n return this._message;\n }\n\n get error(): WcsBroadcastErrorDetail | null {\n return this._error;\n }\n\n // --- Lifecycle (§3.5) ---\n\n // observe() establishes monitoring. BroadcastChannel is command-driven (the\n // Shell calls open(name) from connectedCallback / attributeChangedCallback),\n // so there is no subscription for observe() to set up here: it is an idempotent\n // no-op that resolves once ready. It exists for skeleton symmetry with the\n // monitor-style nodes so a host can uniformly await observe() == ready.\n observe(): Promise<void> {\n return this._ready;\n }\n\n // --- State setters with event dispatch ---\n\n // Deliberately NO same-value guard (unlike `error` below). A received message\n // is an event, not idempotent state: a peer posting the same value twice is\n // two distinct occurrences and must re-fire wcs-broadcast:message each time so\n // a `message:` binding and any `eventToken.message:` subscriber see both.\n private _setMessage(message: any): void {\n this._message = message;\n this._target.dispatchEvent(new CustomEvent(\"wcs-broadcast:message\", {\n detail: message,\n bubbles: true,\n }));\n }\n\n private _setError(error: WcsBroadcastErrorDetail | null): void {\n // Same-value guard. `error` has no derived state, so suppressing redundant\n // null→null dispatches (e.g. a successful open clearing an already-null\n // error) avoids spurious events. Reference identity is sufficient: each\n // failure builds a fresh object, and the clear path always passes null.\n if (this._error === error) return;\n this._error = error;\n this._target.dispatchEvent(new CustomEvent(\"wcs-broadcast:error\", {\n detail: error,\n bubbles: true,\n }));\n }\n\n // --- Public API ---\n\n /**\n * Join the named channel. Any previously-open channel is closed first, so\n * calling `open()` again switches channels. When the BroadcastChannel\n * constructor is unavailable this surfaces an `unsupported` error and leaves\n * the Core channel-less (a later `post()` then errors loudly rather than\n * silently dropping).\n */\n open(name: string): void {\n if (!this._hasBroadcastChannel()) {\n this._setError(this._unsupportedError());\n return;\n }\n // Idempotent on the same channel: re-opening the channel we are already on\n // is pure churn (BroadcastChannel has no reconnect semantics). This also\n // absorbs the custom-element *upgrade* path — when a connected element with\n // a `name` attribute is upgraded (autoloader defines the tag after the\n // markup exists), the spec fires attributeChangedCallback (isConnected ===\n // true) *and* connectedCallback, so the Shell calls open() twice. Without\n // this guard that would create a channel and immediately tear it down.\n if (this._channel && this._name === name) return;\n this._closeChannel();\n this._setError(null);\n // Capture the generation for this channel (§3.4). The listeners below close\n // over `gen`; an event that fires after dispose() (which bumps _gen) is\n // recognised as stale and dropped without writing state to a torn-down\n // element. The handlers are stored so _closeChannel() can remove them by the\n // same reference.\n const gen = ++this._gen;\n const channel = new BroadcastChannel(name);\n this._onMessage = (event: MessageEvent): void => {\n if (gen !== this._gen) return;\n this._setMessage(event.data);\n };\n // Fired when a peer posted a value this context cannot deserialize. The event\n // carries no usable payload, so report a synthetic DataError.\n this._onMessageError = (): void => {\n if (gen !== this._gen) return;\n this._setError({\n name: \"DataError\",\n message: \"Failed to deserialize a message received on the channel.\",\n });\n };\n channel.addEventListener(\"message\", this._onMessage);\n channel.addEventListener(\"messageerror\", this._onMessageError);\n this._channel = channel;\n this._name = name;\n }\n\n /**\n * Post a structured-cloneable value to every other context on the channel.\n * The local context never receives it (self-exclusion). Never throws:\n * a non-cloneable value surfaces as a `DataCloneError` through `error`, and\n * posting with no open channel surfaces an `InvalidStateError`.\n */\n post(data: any): void {\n if (!this._hasBroadcastChannel()) {\n this._setError(this._unsupportedError());\n return;\n }\n if (!this._channel) {\n this._setError({\n name: \"InvalidStateError\",\n message: \"Channel is not open. Call open(name) before post().\",\n });\n return;\n }\n try {\n this._channel.postMessage(data);\n } catch (err) {\n this._setError(this._normalizeError(err));\n }\n }\n\n /** Leave the channel. Idempotent — a no-op when no channel is open. */\n close(): void {\n this._closeChannel();\n }\n\n /**\n * Tear the Core down for a disconnected Shell: close the channel and reset the\n * error shadow silently (no dispatch on a torn-down element). A later\n * reconnect re-opens via the Shell's connectedCallback.\n *\n * Asymmetry by design: `_message` is deliberately NOT reset. `error` is\n * transient connection state — a stale error from a previous channel would be\n * misleading after a reconnect, so it is cleared. `message` is the last value\n * received (an event payload), not connection state; it is retained as the\n * Core's last-known datum so a binding still reads it across a disconnect/\n * reconnect, and it is naturally overwritten by the next incoming message.\n */\n dispose(): void {\n // Bump the generation first (§3.4) so any message/messageerror that drains\n // after teardown is recognised as stale, then close the channel and reset the\n // error shadow silently.\n this._gen++;\n this._closeChannel();\n this._error = null;\n }\n\n // --- Internal ---\n\n // Per-channel listeners, (re)created in open() so each closes over its own\n // generation (§3.4). null while no channel is open; the real handlers are\n // installed by open() and removed by the same reference in _closeChannel().\n private _onMessage: ((event: MessageEvent) => void) | null = null;\n private _onMessageError: (() => void) | null = null;\n\n private _closeChannel(): void {\n if (!this._channel) return;\n this._channel.removeEventListener(\"message\", this._onMessage!);\n this._channel.removeEventListener(\"messageerror\", this._onMessageError!);\n this._channel.close();\n this._channel = null;\n this._name = null;\n this._onMessage = null;\n this._onMessageError = null;\n }\n\n private _hasBroadcastChannel(): boolean {\n return typeof BroadcastChannel !== \"undefined\";\n }\n\n private _normalizeError(err: unknown): WcsBroadcastErrorDetail {\n if (err instanceof Error) {\n // DOMException is an Error subclass; its `name` (DataCloneError, etc.) is\n // the meaningful discriminator for consumers switching on failure kind.\n return { name: err.name, message: err.message };\n }\n return { name: \"Error\", message: String(err) };\n }\n\n private _unsupportedError(): WcsBroadcastErrorDetail {\n return {\n name: \"NotSupportedError\",\n message: \"BroadcastChannel is not available in this environment.\",\n };\n }\n}\n","import { config } from \"./config.js\";\nimport type { WcsBroadcast } from \"./components/Broadcast.js\";\n\nlet registered = false;\n\n// Attribute names for the optional post-on-click DOM trigger (clipboard.js-style\n// DX). The element carrying `data-broadcast-target` points at a <wcs-broadcast>\n// by id; the text to post comes from either a literal `data-broadcast-text` or\n// a `data-broadcast-from` CSS selector resolving to a source element.\nconst TEXT_ATTRIBUTE = \"data-broadcast-text\";\nconst FROM_ATTRIBUTE = \"data-broadcast-from\";\n\nfunction resolveText(triggerElement: Element): string | null {\n // Literal text wins when present (including an empty string — posting \"\" is a\n // legitimate request). The `?? \"\"` right-hand side is defensive and\n // unreachable: hasAttribute() just returned true, so getAttribute() cannot be\n // null here. It exists only to satisfy the `string | null` return type — do\n // not chase coverage on it (the DOM contract makes the null branch impossible).\n if (triggerElement.hasAttribute(TEXT_ATTRIBUTE)) {\n return triggerElement.getAttribute(TEXT_ATTRIBUTE) ?? \"\";\n }\n const selector = triggerElement.getAttribute(FROM_ATTRIBUTE);\n if (!selector) return null;\n // A user-authored selector can be syntactically invalid (e.g. `[data-*` or a\n // bare `:not()`), which makes querySelector throw a SyntaxError. Swallow it\n // and treat the source as unresolvable — the same \"nothing to post\" path as a\n // selector that matches no element — so one bad attribute never crashes the\n // document-level click handler and kills autoTrigger for the whole tab.\n let source: Element | null;\n try {\n source = document.querySelector(selector);\n } catch {\n return null;\n }\n if (!source) return null;\n // Read a form control's `value`; fall back to text content. A bare\n // `\"value\" in source` check is too broad — it also matches <button>,\n // <li value>, <progress>, etc. (which carry an unrelated `value`), posting the\n // wrong thing. Narrow to the text-bearing controls a user actually points\n // `data-broadcast-from` at; everything else falls through to textContent.\n if (\n source instanceof HTMLInputElement ||\n source instanceof HTMLTextAreaElement ||\n source instanceof HTMLSelectElement\n ) {\n return source.value;\n }\n // `?? \"\"` is defensive: per the DOM spec only Document / DocumentType /\n // Notation nodes have a null `textContent`, and querySelector only ever\n // returns an Element (whose textContent is always a string). The branch is\n // therefore unreachable in practice and kept solely for the `string | null`\n // type — not worth a contrived test.\n return source.textContent ?? \"\";\n}\n\nfunction handleClick(event: Event): void {\n const target = event.target;\n if (!(target instanceof Element)) return;\n\n const triggerElement = target.closest<Element>(`[${config.triggerAttribute}]`);\n if (!triggerElement) return;\n\n const broadcastId = triggerElement.getAttribute(config.triggerAttribute);\n if (!broadcastId) return;\n\n // Resolve the registered constructor at call time instead of importing\n // WcsBroadcast as a value (avoids a components ⇄ autoTrigger import cycle:\n // Broadcast.connectedCallback() calls registerAutoTrigger()). instanceof\n // against the customElements registry keeps the same identity guarantee.\n const BroadcastCtor = customElements.get(config.tagNames.broadcast);\n const broadcastElement = document.getElementById(broadcastId);\n if (!BroadcastCtor || !(broadcastElement instanceof BroadcastCtor)) return;\n\n const text = resolveText(triggerElement);\n // No resolvable source: leave the click alone (do not preventDefault) so the\n // element's default action is unaffected.\n if (text === null) return;\n\n // Suppress the default action so a post can run without navigating. Intentional:\n // do not attach data-broadcast-target to an element whose default action you\n // also want (a real <a href> link). See README \"Optional DOM Triggering\".\n event.preventDefault();\n (broadcastElement as WcsBroadcast).post(text);\n}\n\nexport function registerAutoTrigger(): void {\n if (registered) return;\n registered = true;\n document.addEventListener(\"click\", handleClick);\n}\n\nexport function unregisterAutoTrigger(): void {\n if (!registered) return;\n registered = false;\n document.removeEventListener(\"click\", handleClick);\n}\n","import { config } from \"../config.js\";\nimport { IWcBindable, WcsBroadcastErrorDetail } from \"../types.js\";\nimport { BroadcastCore } from \"../core/BroadcastCore.js\";\nimport { registerAutoTrigger } from \"../autoTrigger.js\";\n\n// Named WcsBroadcast (not `Broadcast`) to match the <wcs-clipboard> WcsClipboard\n// / <wcs-ws> WcsWebSocket convention and avoid shadowing any global.\nexport class WcsBroadcast extends HTMLElement {\n // SSR (§4.4): the channel opens synchronously in connectedCallback, so the\n // Core's observe() resolves immediately; we still expose connectedCallbackPromise\n // so a state binder can uniformly await readiness before snapshotting.\n static hasConnectedCallbackPromise = true;\n static wcBindable: IWcBindable = {\n ...BroadcastCore.wcBindable,\n // Shell-level settable surface. `name` selects the channel; `manual`\n // suppresses auto-open on connect. There is no momentary `post` property:\n // posting needs an argument (the payload), so element actions run via\n // command-token (`command.post: $command.ping`) or the DOM autoTrigger, not\n // a value-derived setter — keeping `post` a plain command keeps the\n // command-token wiring (`command.post:`) readable.\n inputs: [\n { name: \"name\", attribute: \"name\" },\n { name: \"manual\", attribute: \"manual\" },\n ],\n // Commands are identical to the Core's — no rename needed since the `name` /\n // `manual` attribute accessors do not collide with open/post/close.\n commands: BroadcastCore.wcBindable.commands,\n };\n static get observedAttributes(): string[] { return [\"name\"]; }\n\n private _core: BroadcastCore;\n private _connectedCallbackPromise: Promise<void> = Promise.resolve();\n private _internals: ElementInternals | null = null;\n\n constructor() {\n super();\n this._core = new BroadcastCore(this);\n this._internals = this._initInternals();\n this._wireStates({\n \"wcs-broadcast:error\": (d) => ({ error: d != null }),\n });\n }\n\n // CSS state reflection (:state()) — debug-only snapshot getter. NOT part of\n // wc-bindable (not a bind target); see README \"CSS styling with :state()\".\n // MUST NOT return the live CustomStateSet (that would let callers write\n // states from outside, defeating the point of :state() being read-only).\n get debugStates(): string[] {\n return this._internals ? [...this._internals.states] : [];\n }\n\n private _initInternals(): ElementInternals | null {\n // never-throw (async-io-node-guidelines.md §3.6): attachInternals is absent\n // in happy-dom / older environments, and pre-125 Chromium rejects\n // non-dashed state names from states.add() (probed and discarded here).\n // Either case silently disables reflection — the component still works,\n // it just doesn't expose :state() selectors.\n try {\n if (typeof this.attachInternals !== \"function\") return null;\n const internals = this.attachInternals();\n internals.states.add(\"wcs-probe\");\n internals.states.delete(\"wcs-probe\");\n return internals;\n } catch {\n return null;\n }\n }\n\n private _wireStates(map: Record<string, (detail: any) => Record<string, boolean>>): void {\n if (this._internals === null) return;\n const states = this._internals.states;\n for (const [event, toStates] of Object.entries(map)) {\n this.addEventListener(event, (e) => {\n const debug = this.hasAttribute(\"debug-states\");\n for (const [name, on] of Object.entries(toStates((e as CustomEvent).detail))) {\n try {\n if (on) { states.add(name); } else { states.delete(name); }\n } catch { /* never-throw */ }\n if (debug) this.toggleAttribute(`data-wcs-state-${name}`, on);\n }\n });\n }\n }\n\n get connectedCallbackPromise(): Promise<void> {\n return this._connectedCallbackPromise;\n }\n\n // --- Attribute accessors ---\n\n get name(): string {\n return this.getAttribute(\"name\") || \"\";\n }\n\n set name(value: string) {\n this.setAttribute(\"name\", value);\n }\n\n get manual(): boolean {\n return this.hasAttribute(\"manual\");\n }\n\n set manual(value: boolean) {\n if (value) {\n this.setAttribute(\"manual\", \"\");\n } else {\n this.removeAttribute(\"manual\");\n }\n }\n\n // --- Core delegated getters ---\n\n get message(): any {\n return this._core.message;\n }\n\n get error(): WcsBroadcastErrorDetail | null {\n return this._core.error;\n }\n\n // --- Commands ---\n\n open(): void {\n if (this.name) {\n this._core.open(this.name);\n }\n }\n\n post(data: any): void {\n this._core.post(data);\n }\n\n close(): void {\n this._core.close();\n }\n\n // --- Lifecycle ---\n\n attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null): void {\n if (name === \"name\" && this.isConnected && !this.manual && newValue) {\n this._core.open(newValue);\n }\n }\n\n connectedCallback(): void {\n this.style.display = \"none\";\n if (config.autoTrigger) {\n registerAutoTrigger();\n }\n if (!this.manual && this.name) {\n this._core.open(this.name);\n }\n // SSR (§4.4): expose the Core's readiness as connectedCallbackPromise. The\n // channel opens synchronously above, so observe() resolves immediately.\n this._connectedCallbackPromise = this._core.observe();\n }\n\n disconnectedCallback(): void {\n // Deliberately does NOT call unregisterAutoTrigger(). The autoTrigger click\n // listener is a single process-wide document listener (registerAutoTrigger\n // is idempotent), shared by every <wcs-broadcast> on the page — not owned by\n // this element. Tearing it down when the last element disconnects would\n // break a later-inserted trigger, so it is intentionally left installed for\n // the document's lifetime (one passive listener, negligible cost). This\n // mirrors <wcs-clipboard>, which registers but never unregisters either.\n // unregisterAutoTrigger stays exported purely as a symmetric teardown hook\n // for tests / advanced manual control; the production lifecycle never calls\n // it.\n this._core.dispose();\n }\n}\n","import { setConfig } from \"./config.js\";\nimport { registerComponents } from \"./registerComponents.js\";\nimport { IWritableConfig } from \"./types.js\";\n\nexport function bootstrapBroadcast(userConfig?: IWritableConfig): void {\n if (userConfig) {\n setConfig(userConfig);\n }\n registerComponents();\n}\n","import { WcsBroadcast } from \"./components/Broadcast.js\";\nimport { config } from \"./config.js\";\n\nexport function registerComponents(): void {\n if (!customElements.get(config.tagNames.broadcast)) {\n customElements.define(config.tagNames.broadcast, WcsBroadcast);\n }\n}\n"],"names":["_config","autoTrigger","triggerAttribute","tagNames","broadcast","deepFreeze","obj","Object","freeze","key","keys","deepClone","clone","frozenConfig","config","getConfig","BroadcastCore","EventTarget","static","protocol","version","properties","name","event","commands","_target","_channel","_name","_message","_error","_gen","_ready","Promise","resolve","constructor","target","super","this","ready","message","error","observe","_setMessage","dispatchEvent","CustomEvent","detail","bubbles","_setError","open","_hasBroadcastChannel","_unsupportedError","_closeChannel","gen","channel","BroadcastChannel","_onMessage","data","_onMessageError","addEventListener","post","postMessage","err","_normalizeError","close","dispose","removeEventListener","Error","String","registered","TEXT_ATTRIBUTE","handleClick","Element","triggerElement","closest","broadcastId","getAttribute","BroadcastCtor","customElements","get","broadcastElement","document","getElementById","text","hasAttribute","selector","source","querySelector","HTMLInputElement","HTMLTextAreaElement","HTMLSelectElement","value","textContent","resolveText","preventDefault","WcsBroadcast","HTMLElement","wcBindable","inputs","attribute","observedAttributes","_core","_connectedCallbackPromise","_internals","_initInternals","_wireStates","d","debugStates","states","attachInternals","internals","add","delete","map","toStates","entries","e","debug","on","toggleAttribute","connectedCallbackPromise","setAttribute","manual","removeAttribute","attributeChangedCallback","_oldValue","newValue","isConnected","connectedCallback","style","display","disconnectedCallback","bootstrapBroadcast","userConfig","partialConfig","assign","define"],"mappings":"AAUA,MAAMA,EAA2B,CAC/BC,aAAa,EACbC,iBAAkB,wBAClBC,SAAU,CACRC,UAAW,kBAIf,SAASC,EAAcC,GACrB,GAAY,OAARA,GAA+B,iBAARA,EAAkB,OAAOA,EACpDC,OAAOC,OAAOF,GACd,IAAK,MAAMG,KAAOF,OAAOG,KAAKJ,GAC5BD,EAAYC,EAAgCG,IAE9C,OAAOH,CACT,CAEA,SAASK,EAAaL,GACpB,GAAY,OAARA,GAA+B,iBAARA,EAAkB,OAAOA,EACpD,MAAMM,EAAiC,CAAA,EACvC,IAAK,MAAMH,KAAOF,OAAOG,KAAKJ,GAC5BM,EAAMH,GAAOE,EAAWL,EAAgCG,IAE1D,OAAOG,CACT,CAEA,IAAIC,EAA+B,KAM5B,MAAMC,EAAkBd,WAEfe,IAId,OAHKF,IACHA,EAAeR,EAAWM,EAAUX,KAE/Ba,CACT,CCzBM,MAAOG,UAAsBC,YACjCC,kBAAiC,CAC/BC,SAAU,cACVC,QAAS,EACTC,WAAY,CACV,CAAEC,KAAM,UAAWC,MAAO,yBAC1B,CAAED,KAAM,QAASC,MAAO,wBAE1BC,SAAU,CACR,CAAEF,KAAM,QACR,CAAEA,KAAM,QACR,CAAEA,KAAM,WAIJG,QACAC,SAAoC,KACpCC,MAAuB,KACvBC,SAAgB,KAChBC,OAAyC,KAMzCC,KAAO,EAGPC,OAAwBC,QAAQC,UAExC,WAAAC,CAAYC,GACVC,QACAC,KAAKZ,QAAUU,GAAUE,IAC3B,CAEA,SAAIC,GACF,OAAOD,KAAKN,MACd,CAEA,WAAIQ,GACF,OAAOF,KAAKT,QACd,CAEA,SAAIY,GACF,OAAOH,KAAKR,MACd,CASA,OAAAY,GACE,OAAOJ,KAAKN,MACd,CAQQ,WAAAW,CAAYH,GAClBF,KAAKT,SAAWW,EAChBF,KAAKZ,QAAQkB,cAAc,IAAIC,YAAY,wBAAyB,CAClEC,OAAQN,EACRO,SAAS,IAEb,CAEQ,SAAAC,CAAUP,GAKZH,KAAKR,SAAWW,IACpBH,KAAKR,OAASW,EACdH,KAAKZ,QAAQkB,cAAc,IAAIC,YAAY,sBAAuB,CAChEC,OAAQL,EACRM,SAAS,KAEb,CAWA,IAAAE,CAAK1B,GACH,IAAKe,KAAKY,uBAER,YADAZ,KAAKU,UAAUV,KAAKa,qBAUtB,GAAIb,KAAKX,UAAYW,KAAKV,QAAUL,EAAM,OAC1Ce,KAAKc,gBACLd,KAAKU,UAAU,MAMf,MAAMK,IAAQf,KAAKP,KACbuB,EAAU,IAAIC,iBAAiBhC,GACrCe,KAAKkB,WAAchC,IACb6B,IAAQf,KAAKP,MACjBO,KAAKK,YAAYnB,EAAMiC,OAIzBnB,KAAKoB,gBAAkB,KACjBL,IAAQf,KAAKP,MACjBO,KAAKU,UAAU,CACbzB,KAAM,YACNiB,QAAS,8DAGbc,EAAQK,iBAAiB,UAAWrB,KAAKkB,YACzCF,EAAQK,iBAAiB,eAAgBrB,KAAKoB,iBAC9CpB,KAAKX,SAAW2B,EAChBhB,KAAKV,MAAQL,CACf,CAQA,IAAAqC,CAAKH,GACH,GAAKnB,KAAKY,uBAIV,GAAKZ,KAAKX,SAOV,IACEW,KAAKX,SAASkC,YAAYJ,EAC5B,CAAE,MAAOK,GACPxB,KAAKU,UAAUV,KAAKyB,gBAAgBD,GACtC,MAVExB,KAAKU,UAAU,CACbzB,KAAM,oBACNiB,QAAS,6DANXF,KAAKU,UAAUV,KAAKa,oBAexB,CAGA,KAAAa,GACE1B,KAAKc,eACP,CAcA,OAAAa,GAIE3B,KAAKP,OACLO,KAAKc,gBACLd,KAAKR,OAAS,IAChB,CAOQ0B,WAAqD,KACrDE,gBAAuC,KAEvC,aAAAN,GACDd,KAAKX,WACVW,KAAKX,SAASuC,oBAAoB,UAAW5B,KAAKkB,YAClDlB,KAAKX,SAASuC,oBAAoB,eAAgB5B,KAAKoB,iBACvDpB,KAAKX,SAASqC,QACd1B,KAAKX,SAAW,KAChBW,KAAKV,MAAQ,KACbU,KAAKkB,WAAa,KAClBlB,KAAKoB,gBAAkB,KACzB,CAEQ,oBAAAR,GACN,MAAmC,oBAArBK,gBAChB,CAEQ,eAAAQ,CAAgBD,GACtB,OAAIA,aAAeK,MAGV,CAAE5C,KAAMuC,EAAIvC,KAAMiB,QAASsB,EAAItB,SAEjC,CAAEjB,KAAM,QAASiB,QAAS4B,OAAON,GAC1C,CAEQ,iBAAAX,GACN,MAAO,CACL5B,KAAM,oBACNiB,QAAS,yDAEb,ECpPF,IAAI6B,GAAa,EAMjB,MAAMC,EAAiB,sBA8CvB,SAASC,EAAY/C,GACnB,MAAMY,EAASZ,EAAMY,OACrB,KAAMA,aAAkBoC,SAAU,OAElC,MAAMC,EAAiBrC,EAAOsC,QAAiB,IAAI3D,EAAOZ,qBAC1D,IAAKsE,EAAgB,OAErB,MAAME,EAAcF,EAAeG,aAAa7D,EAAOZ,kBACvD,IAAKwE,EAAa,OAMlB,MAAME,EAAgBC,eAAeC,IAAIhE,EAAOX,SAASC,WACnD2E,EAAmBC,SAASC,eAAeP,GACjD,KAAKE,GAAmBG,aAA4BH,GAAgB,OAEpE,MAAMM,EA7DR,SAAqBV,GAMnB,GAAIA,EAAeW,aAAad,GAC9B,OAAOG,EAAeG,aAAaN,IAAmB,GAExD,MAAMe,EAAWZ,EAAeG,aAXX,uBAYrB,IAAKS,EAAU,OAAO,KAMtB,IAAIC,EACJ,IACEA,EAASL,SAASM,cAAcF,EAClC,CAAE,MACA,OAAO,IACT,CACA,OAAKC,EAOHA,aAAkBE,kBAClBF,aAAkBG,qBAClBH,aAAkBI,kBAEXJ,EAAOK,MAOTL,EAAOM,aAAe,GAlBT,IAmBtB,CAoBeC,CAAYpB,GAGZ,OAATU,IAKJ3D,EAAMsE,iBACLd,EAAkCpB,KAAKuB,GAC1C,CC5EM,MAAOY,UAAqBC,YAIhC7E,oCAAqC,EACrCA,kBAAiC,IAC5BF,EAAcgF,WAOjBC,OAAQ,CACN,CAAE3E,KAAM,OAAQ4E,UAAW,QAC3B,CAAE5E,KAAM,SAAU4E,UAAW,WAI/B1E,SAAUR,EAAcgF,WAAWxE,UAErC,6BAAW2E,GAAiC,MAAO,CAAC,OAAS,CAErDC,MACAC,0BAA2CrE,QAAQC,UACnDqE,WAAsC,KAE9C,WAAApE,GACEE,QACAC,KAAK+D,MAAQ,IAAIpF,EAAcqB,MAC/BA,KAAKiE,WAAajE,KAAKkE,iBACvBlE,KAAKmE,YAAY,CACf,sBAAwBC,IAAC,CAAQjE,MAAY,MAALiE,KAE5C,CAMA,eAAIC,GACF,OAAOrE,KAAKiE,WAAa,IAAIjE,KAAKiE,WAAWK,QAAU,EACzD,CAEQ,cAAAJ,GAMN,IACE,GAAoC,mBAAzBlE,KAAKuE,gBAAgC,OAAO,KACvD,MAAMC,EAAYxE,KAAKuE,kBAGvB,OAFAC,EAAUF,OAAOG,IAAI,aACrBD,EAAUF,OAAOI,OAAO,aACjBF,CACT,CAAE,MACA,OAAO,IACT,CACF,CAEQ,WAAAL,CAAYQ,GAClB,GAAwB,OAApB3E,KAAKiE,WAAqB,OAC9B,MAAMK,EAAStE,KAAKiE,WAAWK,OAC/B,IAAK,MAAOpF,EAAO0F,KAAa1G,OAAO2G,QAAQF,GAC7C3E,KAAKqB,iBAAiBnC,EAAQ4F,IAC5B,MAAMC,EAAQ/E,KAAK8C,aAAa,gBAChC,IAAK,MAAO7D,EAAM+F,KAAO9G,OAAO2G,QAAQD,EAAUE,EAAkBtE,SAAU,CAC5E,IACMwE,EAAMV,EAAOG,IAAIxF,GAAgBqF,EAAOI,OAAOzF,EACrD,CAAE,MAA0B,CACxB8F,GAAO/E,KAAKiF,gBAAgB,kBAAkBhG,IAAQ+F,EAC5D,GAGN,CAEA,4BAAIE,GACF,OAAOlF,KAAKgE,yBACd,CAIA,QAAI/E,GACF,OAAOe,KAAKsC,aAAa,SAAW,EACtC,CAEA,QAAIrD,CAAKoE,GACPrD,KAAKmF,aAAa,OAAQ9B,EAC5B,CAEA,UAAI+B,GACF,OAAOpF,KAAK8C,aAAa,SAC3B,CAEA,UAAIsC,CAAO/B,GACLA,EACFrD,KAAKmF,aAAa,SAAU,IAE5BnF,KAAKqF,gBAAgB,SAEzB,CAIA,WAAInF,GACF,OAAOF,KAAK+D,MAAM7D,OACpB,CAEA,SAAIC,GACF,OAAOH,KAAK+D,MAAM5D,KACpB,CAIA,IAAAQ,GACMX,KAAKf,MACPe,KAAK+D,MAAMpD,KAAKX,KAAKf,KAEzB,CAEA,IAAAqC,CAAKH,GACHnB,KAAK+D,MAAMzC,KAAKH,EAClB,CAEA,KAAAO,GACE1B,KAAK+D,MAAMrC,OACb,CAIA,wBAAA4D,CAAyBrG,EAAcsG,EAA0BC,GAClD,SAATvG,GAAmBe,KAAKyF,cAAgBzF,KAAKoF,QAAUI,GACzDxF,KAAK+D,MAAMpD,KAAK6E,EAEpB,CAEA,iBAAAE,GACE1F,KAAK2F,MAAMC,QAAU,OACjBnH,EAAOb,cD5DTmE,IACJA,GAAa,EACbY,SAAStB,iBAAiB,QAASY,MC6D5BjC,KAAKoF,QAAUpF,KAAKf,MACvBe,KAAK+D,MAAMpD,KAAKX,KAAKf,MAIvBe,KAAKgE,0BAA4BhE,KAAK+D,MAAM3D,SAC9C,CAEA,oBAAAyF,GAWE7F,KAAK+D,MAAMpC,SACb,ECrKI,SAAUmE,EAAmBC,GJ+C7B,IAAoBC,EI9CpBD,IJ+CqC,kBADjBC,EI7CZD,GJ8CanI,cACvBD,EAAQC,YAAcoI,EAAcpI,aAEQ,iBAAnCoI,EAAcnI,mBACvBF,EAAQE,iBAAmBmI,EAAcnI,kBAEvCmI,EAAclI,UAChBI,OAAO+H,OAAOtI,EAAQG,SAAUkI,EAAclI,UAEhDU,EAAe,MKzDVgE,eAAeC,IAAIhE,EAAOX,SAASC,YACtCyE,eAAe0D,OAAOzH,EAAOX,SAASC,UAAW0F,EDIrD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wcstack/broadcast",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.0",
|
|
4
4
|
"description": "Declarative cross-tab messaging component for Web Components. Framework-agnostic BroadcastChannel primitive via wc-bindable-protocol.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.esm.js",
|