@wcstack/broadcast 1.14.0 → 1.16.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 +1 -1
- package/README.md +1 -1
- package/dist/index.d.ts +29 -21
- package/dist/index.esm.js +64 -14
- 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
|
@@ -151,7 +151,7 @@ post は DOM クリック(autoTrigger)または command-token から起動
|
|
|
151
151
|
- **自己除外は意図的。** あるコンテキストは自分自身の post を決して受信しません — これは BroadcastChannel の契約であり、バグではありません。往復を見るには、第 2 のコンテキスト(タブ/iframe/worker、または同じチャンネル名の第 2 の `<wcs-broadcast>`)を listen させてください。*同じ*タブ内の 2 つの `<wcs-broadcast name="x">` 要素は互いに受信します(別々のチャンネルオブジェクトだから)。単一の要素が自分自身に話しかける場合だけは受信しません。
|
|
152
152
|
- **`name` は監視される。** `<wcs-clipboard>` と異なり、`<wcs-broadcast>` は `name` に対して `observedAttributes` を実装します。接続中(かつ非 `manual`)に `name` 属性を変更すると、古いチャンネルを閉じて新しいチャンネルを開きます。`name` をクリアする(空文字列にする、または属性を削除する)のは close *ではありません*。別の `name` に切り替えるか、明示的に `close()` を呼ぶまで、以前開いたチャンネルは維持されます。空でない新しい値だけが切り替えをトリガします。
|
|
153
153
|
- **ワイヤエンコーディングは無い。** ペイロードは structured clone を使うため、JSON の stringify/parse ステップはありません(テキストワイヤで送る `<wcs-ws>` と異なります)。オブジェクトを直接 post すると、受信側はディープコピーを得ます。クローン不可能な値は `error` 経由で `DataCloneError` として失敗します。
|
|
154
|
-
- **接続状態は無い。** BroadcastChannel は構築された瞬間に「開いて」います — connecting/ハンドシェイク段階も、`readyState` も、再接続もありません(必要ないため)。Shell
|
|
154
|
+
- **接続状態は無い。** BroadcastChannel は構築された瞬間に「開いて」います — connecting/ハンドシェイク段階も、`readyState` も、再接続もありません(必要ないため)。Shell は接続時に同期的に開くので `connectedCallbackPromise` は即座に resolve する(解決済み promise)が、`hasConnectedCallbackPromise = true` として公開されており、state バインダ / SSR がスナップショット前に一様に readiness を await できます。
|
|
155
155
|
- **再接続で開き直す。** 要素を削除して再挿入すると `connectedCallback` が再度実行され、`name` 属性(真実の源)からチャンネルを開き直します。`disconnectedCallback` がそれを閉じます。
|
|
156
156
|
- **無言のエラー処理(ゼロログ)。** wcstack 全体のゼロ依存主義に従い、`<wcs-broadcast>` は実行時の失敗に対して一切ログ出力も throw もしません。BroadcastChannel コンストラクタの欠如、クローン不可能な post、デシリアライズ失敗は `error` プロパティ / `wcs-broadcast:error` イベントを通じてのみ表面化します — `post()` は resolve し、決して reject しません。観測・対処するには `error` をバインドしてください。
|
|
157
157
|
|
package/README.md
CHANGED
|
@@ -151,7 +151,7 @@ State-driven invocation uses the command-token protocol:
|
|
|
151
151
|
- **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.
|
|
152
152
|
- **`name` is observed.** Unlike `<wcs-clipboard>`, `<wcs-broadcast>` implements `observedAttributes` for `name`: changing the `name` attribute while connected (and not `manual`) closes the old channel and opens the new one. Clearing the `name` (setting it to an empty string or removing the attribute) is *not* a close: the previously-open channel is kept until you switch to another `name` or call `close()` explicitly. Only a non-empty new value triggers the switch.
|
|
153
153
|
- **No wire encoding.** Payloads use structured clone, so there is no JSON stringify/parse step (unlike `<wcs-ws>`, which sends over a text wire). Post objects directly; receivers get a deep copy. Non-cloneable values fail with `DataCloneError` via `error`.
|
|
154
|
-
- **No connection state.** A BroadcastChannel is "open" the moment it is constructed — there is no connecting/handshake phase, no `readyState`, and no reconnect (none is needed). The Shell opens synchronously on connect, so
|
|
154
|
+
- **No connection state.** A BroadcastChannel is "open" the moment it is constructed — there is no connecting/handshake phase, no `readyState`, and no reconnect (none is needed). The Shell opens synchronously on connect, so its `connectedCallbackPromise` resolves immediately (an already-resolved promise). It is still exposed (`hasConnectedCallbackPromise = true`) so a state binder / SSR can uniformly await readiness before snapshotting.
|
|
155
155
|
- **Reconnect re-opens.** Removing and re-inserting the element runs `connectedCallback` again, re-opening the channel from the `name` attribute (the source of truth), and `disconnectedCallback` closes it.
|
|
156
156
|
- **Silent failure handling (zero-log).** Consistent with the rest of wcstack's zero-dependency philosophy, `<wcs-broadcast>` never logs or throws for runtime failures. A missing BroadcastChannel constructor, a non-cloneable post, or a deserialization failure are surfaced only through the `error` property / `wcs-broadcast:error` event — `post()` resolves and never rejects. Bind `error` to observe and react.
|
|
157
157
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,19 +1,3 @@
|
|
|
1
|
-
interface ITagNames {
|
|
2
|
-
readonly broadcast: string;
|
|
3
|
-
}
|
|
4
|
-
interface IWritableTagNames {
|
|
5
|
-
broadcast?: string;
|
|
6
|
-
}
|
|
7
|
-
interface IConfig {
|
|
8
|
-
readonly autoTrigger: boolean;
|
|
9
|
-
readonly triggerAttribute: string;
|
|
10
|
-
readonly tagNames: ITagNames;
|
|
11
|
-
}
|
|
12
|
-
interface IWritableConfig {
|
|
13
|
-
autoTrigger?: boolean;
|
|
14
|
-
triggerAttribute?: string;
|
|
15
|
-
tagNames?: IWritableTagNames;
|
|
16
|
-
}
|
|
17
1
|
interface IWcBindableProperty {
|
|
18
2
|
readonly name: string;
|
|
19
3
|
readonly event: string;
|
|
@@ -29,11 +13,29 @@ interface IWcBindableCommand {
|
|
|
29
13
|
}
|
|
30
14
|
interface IWcBindable {
|
|
31
15
|
readonly protocol: "wc-bindable";
|
|
32
|
-
readonly version:
|
|
33
|
-
readonly properties: IWcBindableProperty[];
|
|
34
|
-
readonly inputs?: IWcBindableInput[];
|
|
35
|
-
readonly commands?: IWcBindableCommand[];
|
|
16
|
+
readonly version: 1;
|
|
17
|
+
readonly properties: readonly IWcBindableProperty[];
|
|
18
|
+
readonly inputs?: readonly IWcBindableInput[];
|
|
19
|
+
readonly commands?: readonly IWcBindableCommand[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface ITagNames {
|
|
23
|
+
readonly broadcast: string;
|
|
36
24
|
}
|
|
25
|
+
interface IWritableTagNames {
|
|
26
|
+
broadcast?: string;
|
|
27
|
+
}
|
|
28
|
+
interface IConfig {
|
|
29
|
+
readonly autoTrigger: boolean;
|
|
30
|
+
readonly triggerAttribute: string;
|
|
31
|
+
readonly tagNames: ITagNames;
|
|
32
|
+
}
|
|
33
|
+
interface IWritableConfig {
|
|
34
|
+
autoTrigger?: boolean;
|
|
35
|
+
triggerAttribute?: string;
|
|
36
|
+
tagNames?: IWritableTagNames;
|
|
37
|
+
}
|
|
38
|
+
|
|
37
39
|
/**
|
|
38
40
|
* Normalized BroadcastChannel failure. `name` mirrors the `DOMException.name`
|
|
39
41
|
* (e.g. `DataCloneError` when a posted value is not structured-cloneable,
|
|
@@ -47,7 +49,7 @@ interface WcsBroadcastErrorDetail {
|
|
|
47
49
|
}
|
|
48
50
|
/**
|
|
49
51
|
* Value types for BroadcastCore (headless) — the observable state properties.
|
|
50
|
-
* Use with `bind()` from
|
|
52
|
+
* Use with `bind()` from `a wc-bindable binding core` for compile-time type checking.
|
|
51
53
|
*
|
|
52
54
|
* @example
|
|
53
55
|
* ```typescript
|
|
@@ -126,9 +128,13 @@ declare class BroadcastCore extends EventTarget {
|
|
|
126
128
|
private _name;
|
|
127
129
|
private _message;
|
|
128
130
|
private _error;
|
|
131
|
+
private _gen;
|
|
132
|
+
private _ready;
|
|
129
133
|
constructor(target?: EventTarget);
|
|
134
|
+
get ready(): Promise<void>;
|
|
130
135
|
get message(): any;
|
|
131
136
|
get error(): WcsBroadcastErrorDetail | null;
|
|
137
|
+
observe(): Promise<void>;
|
|
132
138
|
private _setMessage;
|
|
133
139
|
private _setError;
|
|
134
140
|
/**
|
|
@@ -174,7 +180,9 @@ declare class WcsBroadcast extends HTMLElement {
|
|
|
174
180
|
static wcBindable: IWcBindable;
|
|
175
181
|
static get observedAttributes(): string[];
|
|
176
182
|
private _core;
|
|
183
|
+
private _connectedCallbackPromise;
|
|
177
184
|
constructor();
|
|
185
|
+
get connectedCallbackPromise(): Promise<void>;
|
|
178
186
|
get name(): string;
|
|
179
187
|
set name(value: string);
|
|
180
188
|
get manual(): boolean;
|
package/dist/index.esm.js
CHANGED
|
@@ -89,16 +89,37 @@ class BroadcastCore extends EventTarget {
|
|
|
89
89
|
_name = null;
|
|
90
90
|
_message = null;
|
|
91
91
|
_error = null;
|
|
92
|
+
// Generation guard (§3.4): bumped on dispose(). An incoming message /
|
|
93
|
+
// messageerror that fires after the Shell disconnected (a peer posted between
|
|
94
|
+
// disconnect and the channel actually closing, or a queued event drains late)
|
|
95
|
+
// has a stale `gen` and MUST NOT write state to a torn-down element. A boolean
|
|
96
|
+
// flag is insufficient: dispose→reconnect would let a stale event slip through.
|
|
97
|
+
_gen = 0;
|
|
98
|
+
// SSR (§3.8): a channel opens synchronously (no asynchronous probe to await),
|
|
99
|
+
// so readiness is immediate.
|
|
100
|
+
_ready = Promise.resolve();
|
|
92
101
|
constructor(target) {
|
|
93
102
|
super();
|
|
94
103
|
this._target = target ?? this;
|
|
95
104
|
}
|
|
105
|
+
get ready() {
|
|
106
|
+
return this._ready;
|
|
107
|
+
}
|
|
96
108
|
get message() {
|
|
97
109
|
return this._message;
|
|
98
110
|
}
|
|
99
111
|
get error() {
|
|
100
112
|
return this._error;
|
|
101
113
|
}
|
|
114
|
+
// --- Lifecycle (§3.5) ---
|
|
115
|
+
// observe() establishes monitoring. BroadcastChannel is command-driven (the
|
|
116
|
+
// Shell calls open(name) from connectedCallback / attributeChangedCallback),
|
|
117
|
+
// so there is no subscription for observe() to set up here: it is an idempotent
|
|
118
|
+
// no-op that resolves once ready. It exists for skeleton symmetry with the
|
|
119
|
+
// monitor-style nodes so a host can uniformly await observe() == ready.
|
|
120
|
+
observe() {
|
|
121
|
+
return this._ready;
|
|
122
|
+
}
|
|
102
123
|
// --- State setters with event dispatch ---
|
|
103
124
|
// Deliberately NO same-value guard (unlike `error` below). A received message
|
|
104
125
|
// is an event, not idempotent state: a peer posting the same value twice is
|
|
@@ -148,7 +169,28 @@ class BroadcastCore extends EventTarget {
|
|
|
148
169
|
return;
|
|
149
170
|
this._closeChannel();
|
|
150
171
|
this._setError(null);
|
|
172
|
+
// Capture the generation for this channel (§3.4). The listeners below close
|
|
173
|
+
// over `gen`; an event that fires after dispose() (which bumps _gen) is
|
|
174
|
+
// recognised as stale and dropped without writing state to a torn-down
|
|
175
|
+
// element. The handlers are stored so _closeChannel() can remove them by the
|
|
176
|
+
// same reference.
|
|
177
|
+
const gen = ++this._gen;
|
|
151
178
|
const channel = new BroadcastChannel(name);
|
|
179
|
+
this._onMessage = (event) => {
|
|
180
|
+
if (gen !== this._gen)
|
|
181
|
+
return;
|
|
182
|
+
this._setMessage(event.data);
|
|
183
|
+
};
|
|
184
|
+
// Fired when a peer posted a value this context cannot deserialize. The event
|
|
185
|
+
// carries no usable payload, so report a synthetic DataError.
|
|
186
|
+
this._onMessageError = () => {
|
|
187
|
+
if (gen !== this._gen)
|
|
188
|
+
return;
|
|
189
|
+
this._setError({
|
|
190
|
+
name: "DataError",
|
|
191
|
+
message: "Failed to deserialize a message received on the channel.",
|
|
192
|
+
});
|
|
193
|
+
};
|
|
152
194
|
channel.addEventListener("message", this._onMessage);
|
|
153
195
|
channel.addEventListener("messageerror", this._onMessageError);
|
|
154
196
|
this._channel = channel;
|
|
@@ -196,21 +238,19 @@ class BroadcastCore extends EventTarget {
|
|
|
196
238
|
* reconnect, and it is naturally overwritten by the next incoming message.
|
|
197
239
|
*/
|
|
198
240
|
dispose() {
|
|
241
|
+
// Bump the generation first (§3.4) so any message/messageerror that drains
|
|
242
|
+
// after teardown is recognised as stale, then close the channel and reset the
|
|
243
|
+
// error shadow silently.
|
|
244
|
+
this._gen++;
|
|
199
245
|
this._closeChannel();
|
|
200
246
|
this._error = null;
|
|
201
247
|
}
|
|
202
248
|
// --- Internal ---
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
_onMessageError = () => {
|
|
209
|
-
this._setError({
|
|
210
|
-
name: "DataError",
|
|
211
|
-
message: "Failed to deserialize a message received on the channel.",
|
|
212
|
-
});
|
|
213
|
-
};
|
|
249
|
+
// Per-channel listeners, (re)created in open() so each closes over its own
|
|
250
|
+
// generation (§3.4). null while no channel is open; the real handlers are
|
|
251
|
+
// installed by open() and removed by the same reference in _closeChannel().
|
|
252
|
+
_onMessage = null;
|
|
253
|
+
_onMessageError = null;
|
|
214
254
|
_closeChannel() {
|
|
215
255
|
if (!this._channel)
|
|
216
256
|
return;
|
|
@@ -219,6 +259,8 @@ class BroadcastCore extends EventTarget {
|
|
|
219
259
|
this._channel.close();
|
|
220
260
|
this._channel = null;
|
|
221
261
|
this._name = null;
|
|
262
|
+
this._onMessage = null;
|
|
263
|
+
this._onMessageError = null;
|
|
222
264
|
}
|
|
223
265
|
_hasBroadcastChannel() {
|
|
224
266
|
return typeof BroadcastChannel !== "undefined";
|
|
@@ -328,9 +370,10 @@ function registerAutoTrigger() {
|
|
|
328
370
|
// Named WcsBroadcast (not `Broadcast`) to match the <wcs-clipboard> WcsClipboard
|
|
329
371
|
// / <wcs-ws> WcsWebSocket convention and avoid shadowing any global.
|
|
330
372
|
class WcsBroadcast extends HTMLElement {
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
|
|
373
|
+
// SSR (§4.4): the channel opens synchronously in connectedCallback, so the
|
|
374
|
+
// Core's observe() resolves immediately; we still expose connectedCallbackPromise
|
|
375
|
+
// so a state binder can uniformly await readiness before snapshotting.
|
|
376
|
+
static hasConnectedCallbackPromise = true;
|
|
334
377
|
static wcBindable = {
|
|
335
378
|
...BroadcastCore.wcBindable,
|
|
336
379
|
// Shell-level settable surface. `name` selects the channel; `manual`
|
|
@@ -349,10 +392,14 @@ class WcsBroadcast extends HTMLElement {
|
|
|
349
392
|
};
|
|
350
393
|
static get observedAttributes() { return ["name"]; }
|
|
351
394
|
_core;
|
|
395
|
+
_connectedCallbackPromise = Promise.resolve();
|
|
352
396
|
constructor() {
|
|
353
397
|
super();
|
|
354
398
|
this._core = new BroadcastCore(this);
|
|
355
399
|
}
|
|
400
|
+
get connectedCallbackPromise() {
|
|
401
|
+
return this._connectedCallbackPromise;
|
|
402
|
+
}
|
|
356
403
|
// --- Attribute accessors ---
|
|
357
404
|
get name() {
|
|
358
405
|
return this.getAttribute("name") || "";
|
|
@@ -404,6 +451,9 @@ class WcsBroadcast extends HTMLElement {
|
|
|
404
451
|
if (!this.manual && this.name) {
|
|
405
452
|
this._core.open(this.name);
|
|
406
453
|
}
|
|
454
|
+
// SSR (§4.4): expose the Core's readiness as connectedCallbackPromise. The
|
|
455
|
+
// channel opens synchronously above, so observe() resolves immediately.
|
|
456
|
+
this._connectedCallbackPromise = this._core.observe();
|
|
407
457
|
}
|
|
408
458
|
disconnectedCallback() {
|
|
409
459
|
// Deliberately does NOT call unregisterAutoTrigger(). The autoTrigger click
|
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\n constructor(target?: EventTarget) {\n super();\n this._target = target ?? this;\n }\n\n get message(): any {\n return this._message;\n }\n\n get error(): WcsBroadcastErrorDetail | null {\n return this._error;\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 const channel = new BroadcastChannel(name);\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 this._closeChannel();\n this._error = null;\n }\n\n // --- Internal ---\n\n private _onMessage = (event: MessageEvent): void => {\n this._setMessage(event.data);\n };\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 private _onMessageError = (): void => {\n this._setError({\n name: \"DataError\",\n message: \"Failed to deserialize a message received on the channel.\",\n });\n };\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 }\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 // The channel opens synchronously in connectedCallback (no async init), so no\n // connectedCallbackPromise is needed — mirrors <wcs-ws>.\n static hasConnectedCallbackPromise = false;\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\n constructor() {\n super();\n this._core = new BroadcastCore(this);\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 }\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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"}
|
|
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":"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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;constructor(e){super(),this._target=e??this}get message(){return this._message}get error(){return this._error}_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=new BroadcastChannel(e);t.addEventListener("message",this._onMessage),
|
|
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 c="data-broadcast-text";function l(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(c))return e.getAttribute(c)??"";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 u 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();constructor(){super(),this._core=new o(this)}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",l))),!this.manual&&this.name&&this._core.open(this.name),this._connectedCallbackPromise=this._core.observe()}disconnectedCallback(){this._core.dispose()}}function h(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,u)}export{o as BroadcastCore,u as WcsBroadcast,h 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\n constructor(target?: EventTarget) {\n super();\n this._target = target ?? this;\n }\n\n get message(): any {\n return this._message;\n }\n\n get error(): WcsBroadcastErrorDetail | null {\n return this._error;\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 const channel = new BroadcastChannel(name);\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 this._closeChannel();\n this._error = null;\n }\n\n // --- Internal ---\n\n private _onMessage = (event: MessageEvent): void => {\n this._setMessage(event.data);\n };\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 private _onMessageError = (): void => {\n this._setError({\n name: \"DataError\",\n message: \"Failed to deserialize a message received on the channel.\",\n });\n };\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 }\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 // The channel opens synchronously in connectedCallback (no async init), so no\n // connectedCallbackPromise is needed — mirrors <wcs-ws>.\n static hasConnectedCallbackPromise = false;\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\n constructor() {\n super();\n this._core = new BroadcastCore(this);\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 }\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","constructor","target","super","this","message","error","_setMessage","dispatchEvent","CustomEvent","detail","bubbles","_setError","open","_hasBroadcastChannel","_unsupportedError","_closeChannel","channel","BroadcastChannel","addEventListener","_onMessage","_onMessageError","post","data","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","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\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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wcstack/broadcast",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.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",
|