signalk-instrument-widgets 0.5.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.
@@ -0,0 +1,536 @@
1
+ (() => {
2
+ var __defProp = Object.defineProperty;
3
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
+
6
+ // ../signalk-plotterext-bus/dist/chunk-ZYQKQSOC.js
7
+ var BUS_ID = "plotterExt/1";
8
+ var RPC_ERRORS = {
9
+ PARSE_ERROR: -32700,
10
+ INVALID_REQUEST: -32600,
11
+ METHOD_NOT_FOUND: -32601,
12
+ INVALID_PARAMS: -32602,
13
+ INTERNAL_ERROR: -32603,
14
+ HOST_ERROR: -32e3,
15
+ TIMEOUT: -32001,
16
+ CONNECTION_CLOSED: -32002
17
+ };
18
+ var RpcError = class _RpcError extends Error {
19
+ constructor(message, opts = {}) {
20
+ super(message);
21
+ __publicField(this, "code");
22
+ __publicField(this, "data");
23
+ this.name = "RpcError";
24
+ this.code = opts.code ?? RPC_ERRORS.HOST_ERROR;
25
+ const data = { ...opts.data ?? {} };
26
+ if (opts.reason !== void 0) data.reason = opts.reason;
27
+ this.data = Object.keys(data).length > 0 ? data : void 0;
28
+ }
29
+ get reason() {
30
+ return typeof this.data?.reason === "string" ? this.data.reason : void 0;
31
+ }
32
+ toErrorObject() {
33
+ return {
34
+ code: this.code,
35
+ message: this.message,
36
+ ...this.data ? { data: this.data } : {}
37
+ };
38
+ }
39
+ static fromErrorObject(err) {
40
+ return new _RpcError(err.message, { code: err.code, data: err.data });
41
+ }
42
+ /** Normalize any thrown value into an RpcError suitable for the wire. */
43
+ static from(err) {
44
+ if (err instanceof _RpcError) return err;
45
+ if (err instanceof Error) {
46
+ return new _RpcError(err.message, { code: RPC_ERRORS.INTERNAL_ERROR });
47
+ }
48
+ return new _RpcError(String(err), { code: RPC_ERRORS.INTERNAL_ERROR });
49
+ }
50
+ };
51
+ var EVENT_READY = "bus.ready";
52
+ var EVENT_HANDSHAKE = "bus.handshake";
53
+ function matchesPattern(pattern, name) {
54
+ if (pattern === name) return true;
55
+ return match(pattern.split("."), 0, name.split("."), 0);
56
+ }
57
+ function match(p, pi, n, ni) {
58
+ while (pi < p.length) {
59
+ const seg = p[pi];
60
+ if (seg === "**") {
61
+ if (pi === p.length - 1) return true;
62
+ for (let skip = ni; skip <= n.length; skip++) {
63
+ if (match(p, pi + 1, n, skip)) return true;
64
+ }
65
+ return false;
66
+ }
67
+ if (ni >= n.length) return false;
68
+ if (seg !== "*" && seg !== n[ni]) return false;
69
+ pi++;
70
+ ni++;
71
+ }
72
+ return ni === n.length;
73
+ }
74
+ function matchesAny(patterns, name) {
75
+ for (const pattern of patterns) {
76
+ if (matchesPattern(pattern, name)) return true;
77
+ }
78
+ return false;
79
+ }
80
+ function wrap(msg) {
81
+ return { bus: BUS_ID, msg };
82
+ }
83
+ function unwrap(data) {
84
+ if (typeof data !== "object" || data === null) return null;
85
+ const env = data;
86
+ if (env.bus !== BUS_ID) return null;
87
+ return isJsonRpcMessage(env.msg) ? env.msg : null;
88
+ }
89
+ function isJsonRpcMessage(v) {
90
+ if (typeof v !== "object" || v === null) return false;
91
+ const m = v;
92
+ if (m.jsonrpc !== "2.0") return false;
93
+ if (typeof m.method === "string") {
94
+ return m.id === void 0 || typeof m.id === "string" || typeof m.id === "number";
95
+ }
96
+ const idOk = typeof m.id === "string" || typeof m.id === "number" || m.id === null;
97
+ if (!idOk) return false;
98
+ const hasResult = "result" in m;
99
+ const err = m.error;
100
+ const hasError = typeof err === "object" && err !== null && typeof err.code === "number" && typeof err.message === "string";
101
+ return hasResult ? !("error" in m) : hasError;
102
+ }
103
+ function isRequest(msg) {
104
+ return "method" in msg && "id" in msg && msg.id !== void 0;
105
+ }
106
+ function isNotification(msg) {
107
+ return "method" in msg && (!("id" in msg) || msg.id === void 0);
108
+ }
109
+ function isResponse(msg) {
110
+ return !("method" in msg);
111
+ }
112
+ function windowPort(peer, opts = {}) {
113
+ const listenWindow = opts.listenWindow ?? globalThis;
114
+ const origin = opts.origin ?? listenWindow.location?.origin ?? "*";
115
+ return {
116
+ post(data) {
117
+ peer.postMessage(data, origin);
118
+ },
119
+ listen(handler) {
120
+ const fn = (ev) => {
121
+ if (ev.source !== peer) return;
122
+ if (origin !== "*" && ev.origin !== origin) return;
123
+ handler(ev.data);
124
+ };
125
+ listenWindow.addEventListener("message", fn);
126
+ return () => listenWindow.removeEventListener("message", fn);
127
+ }
128
+ };
129
+ }
130
+ var DEFAULT_CALL_TIMEOUT_MS = 1e4;
131
+ var BusEndpoint = class {
132
+ constructor(opts) {
133
+ __publicField(this, "callTimeoutMs");
134
+ __publicField(this, "port");
135
+ __publicField(this, "unlisten");
136
+ __publicField(this, "onError");
137
+ __publicField(this, "pending", /* @__PURE__ */ new Map());
138
+ __publicField(this, "methods", /* @__PURE__ */ new Map());
139
+ __publicField(this, "eventHandlers", /* @__PURE__ */ new Set());
140
+ __publicField(this, "idPrefix", Math.random().toString(36).slice(2, 8));
141
+ __publicField(this, "seq", 0);
142
+ __publicField(this, "closed", false);
143
+ this.port = opts.port;
144
+ this.callTimeoutMs = opts.callTimeoutMs ?? DEFAULT_CALL_TIMEOUT_MS;
145
+ this.onError = opts.onError ?? ((err) => console.warn("[plotterext-bus]", err));
146
+ this.unlisten = this.port.listen((data) => this.onData(data));
147
+ }
148
+ registerMethod(name, handler) {
149
+ this.methods.set(name, handler);
150
+ }
151
+ unregisterMethod(name) {
152
+ this.methods.delete(name);
153
+ }
154
+ /**
155
+ * Handle incoming notifications whose names match any of the wildcard
156
+ * patterns. Returns an unsubscribe function. This is local dispatch only;
157
+ * telling the peer which events to forward is a separate concern
158
+ * (`events.subscribe`).
159
+ */
160
+ onEvent(patterns, fn) {
161
+ const entry = { patterns, fn };
162
+ this.eventHandlers.add(entry);
163
+ return () => this.eventHandlers.delete(entry);
164
+ }
165
+ /** Send a notification (an event) to the peer. */
166
+ notify(method, params) {
167
+ this.send({ jsonrpc: "2.0", method, ...params !== void 0 ? { params } : {} });
168
+ }
169
+ /** Call a method on the peer; resolves with its result. */
170
+ call(method, params, opts = {}) {
171
+ if (this.closed) {
172
+ return Promise.reject(
173
+ new RpcError("Bus endpoint is closed", {
174
+ code: RPC_ERRORS.CONNECTION_CLOSED,
175
+ reason: "CLOSED"
176
+ })
177
+ );
178
+ }
179
+ const id = `${this.idPrefix}-${++this.seq}`;
180
+ const timeoutMs = opts.timeoutMs ?? this.callTimeoutMs;
181
+ return new Promise((resolve, reject) => {
182
+ const timer = timeoutMs > 0 ? setTimeout(() => {
183
+ this.pending.delete(id);
184
+ reject(
185
+ new RpcError(`Call timed out after ${timeoutMs}ms: ${method}`, {
186
+ code: RPC_ERRORS.TIMEOUT,
187
+ reason: "TIMEOUT"
188
+ })
189
+ );
190
+ }, timeoutMs) : null;
191
+ this.pending.set(id, { resolve, reject, timer });
192
+ this.send({
193
+ jsonrpc: "2.0",
194
+ id,
195
+ method,
196
+ ...params !== void 0 ? { params } : {}
197
+ });
198
+ });
199
+ }
200
+ close() {
201
+ if (this.closed) return;
202
+ this.closed = true;
203
+ this.unlisten();
204
+ for (const [, p] of this.pending) {
205
+ if (p.timer) clearTimeout(p.timer);
206
+ p.reject(
207
+ new RpcError("Bus endpoint closed", {
208
+ code: RPC_ERRORS.CONNECTION_CLOSED,
209
+ reason: "CLOSED"
210
+ })
211
+ );
212
+ }
213
+ this.pending.clear();
214
+ this.eventHandlers.clear();
215
+ }
216
+ send(msg) {
217
+ if (this.closed) return;
218
+ this.port.post(wrap(msg));
219
+ }
220
+ onData(data) {
221
+ const msg = unwrap(data);
222
+ if (!msg) return;
223
+ if (isResponse(msg)) {
224
+ this.onResponse(msg);
225
+ } else if (isRequest(msg)) {
226
+ void this.onRequest(msg);
227
+ } else if (isNotification(msg)) {
228
+ this.onNotification(msg.method, msg.params);
229
+ }
230
+ }
231
+ onResponse(msg) {
232
+ if (msg.id === null) return;
233
+ const p = this.pending.get(msg.id);
234
+ if (!p) return;
235
+ this.pending.delete(msg.id);
236
+ if (p.timer) clearTimeout(p.timer);
237
+ if ("error" in msg) {
238
+ p.reject(RpcError.fromErrorObject(msg.error));
239
+ } else {
240
+ p.resolve(msg.result);
241
+ }
242
+ }
243
+ async onRequest(msg) {
244
+ const handler = this.methods.get(msg.method);
245
+ if (!handler) {
246
+ this.send({
247
+ jsonrpc: "2.0",
248
+ id: msg.id,
249
+ error: {
250
+ code: RPC_ERRORS.METHOD_NOT_FOUND,
251
+ message: `Method not found: ${msg.method}`
252
+ }
253
+ });
254
+ return;
255
+ }
256
+ try {
257
+ const result = await handler(msg.params, { endpoint: this });
258
+ this.send({
259
+ jsonrpc: "2.0",
260
+ id: msg.id,
261
+ result: result === void 0 ? null : result
262
+ });
263
+ } catch (err) {
264
+ this.send({
265
+ jsonrpc: "2.0",
266
+ id: msg.id,
267
+ error: RpcError.from(err).toErrorObject()
268
+ });
269
+ }
270
+ }
271
+ onNotification(name, params) {
272
+ for (const entry of [...this.eventHandlers]) {
273
+ if (matchesAny(entry.patterns, name)) {
274
+ try {
275
+ entry.fn(name, params);
276
+ } catch (err) {
277
+ this.onError(err);
278
+ }
279
+ }
280
+ }
281
+ }
282
+ };
283
+
284
+ // ../signalk-plotterext-bus/dist/chunk-EGWZMA5J.js
285
+ var ExtensionClient = class {
286
+ constructor(endpoint, handshake) {
287
+ __publicField(this, "handshake");
288
+ __publicField(this, "endpoint");
289
+ /** Host-persisted key/value state (see spec: State Storage). */
290
+ __publicField(this, "state", {
291
+ get: async (keys, scope) => {
292
+ const result = await this.call("state.get", {
293
+ ...scope ? { scope } : {},
294
+ ...keys ? { keys } : {}
295
+ });
296
+ return result.values ?? {};
297
+ },
298
+ set: async (values, scope) => {
299
+ await this.call("state.set", {
300
+ ...scope ? { scope } : {},
301
+ values
302
+ });
303
+ }
304
+ });
305
+ /** Signal K data relayed by the host (capabilities signalk.stream / .put). */
306
+ __publicField(this, "signalk", {
307
+ /**
308
+ * Subscribe to Signal K path values. The host publishes them as
309
+ * `sk.<path>` events; this helper hides the event-name mapping and
310
+ * establishes both the event-forwarding subscription and the host's
311
+ * upstream Signal K subscription.
312
+ */
313
+ subscribe: async (paths, handler) => {
314
+ const patterns = paths.map((p) => `sk.${p}`);
315
+ const offEvents = await this.subscribe(
316
+ patterns,
317
+ (_name, params) => handler(params)
318
+ );
319
+ let subscriptionId;
320
+ try {
321
+ const result = await this.call("signalk.subscribe", { paths });
322
+ subscriptionId = result.subscriptionId;
323
+ } catch (err) {
324
+ await offEvents();
325
+ throw err;
326
+ }
327
+ return async () => {
328
+ await offEvents();
329
+ await this.call("signalk.unsubscribe", { subscriptionId }).catch(
330
+ () => {
331
+ }
332
+ );
333
+ };
334
+ },
335
+ put: (path, value) => {
336
+ return this.call("signalk.put", { path, value });
337
+ }
338
+ });
339
+ this.endpoint = endpoint;
340
+ this.handshake = handshake;
341
+ }
342
+ get context() {
343
+ return this.handshake.context;
344
+ }
345
+ get apiVersion() {
346
+ return this.handshake.apiVersion;
347
+ }
348
+ get capabilities() {
349
+ return this.handshake.capabilities;
350
+ }
351
+ hasCapability(id) {
352
+ return this.handshake.capabilities.includes(id);
353
+ }
354
+ /** Call a host API method. */
355
+ call(method, params, opts) {
356
+ return this.endpoint.call(method, params, opts);
357
+ }
358
+ /** Send a notification to the host. */
359
+ notify(method, params) {
360
+ this.endpoint.notify(method, params);
361
+ }
362
+ /**
363
+ * Subscribe to host events matching wildcard patterns. Registers both the
364
+ * host-side forwarding subscription and local dispatch; the returned
365
+ * function tears down both.
366
+ */
367
+ async subscribe(patterns, handler) {
368
+ const off = this.endpoint.onEvent(patterns, handler);
369
+ let subscriptionId;
370
+ try {
371
+ const result = await this.call("events.subscribe", { patterns });
372
+ subscriptionId = result.subscriptionId;
373
+ } catch (err) {
374
+ off();
375
+ throw err;
376
+ }
377
+ return async () => {
378
+ off();
379
+ await this.call("events.unsubscribe", { subscriptionId }).catch(() => {
380
+ });
381
+ };
382
+ }
383
+ close() {
384
+ this.endpoint.close();
385
+ }
386
+ };
387
+ function connectExtension(opts = {}) {
388
+ const port = opts.port ?? windowPort(globalThis.parent, {
389
+ origin: "*"
390
+ });
391
+ const endpoint = new BusEndpoint({
392
+ port,
393
+ callTimeoutMs: opts.callTimeoutMs,
394
+ onError: opts.onError
395
+ });
396
+ return new Promise((resolve, reject) => {
397
+ let done = false;
398
+ const off = endpoint.onEvent([EVENT_HANDSHAKE], (_name, params) => {
399
+ if (done) return;
400
+ done = true;
401
+ cleanup();
402
+ resolve(new ExtensionClient(endpoint, params));
403
+ });
404
+ const interval = setInterval(
405
+ () => endpoint.notify(EVENT_READY),
406
+ opts.readyIntervalMs ?? 250
407
+ );
408
+ const timeout = setTimeout(() => {
409
+ if (done) return;
410
+ done = true;
411
+ cleanup();
412
+ endpoint.close();
413
+ reject(
414
+ new RpcError("Timed out waiting for host handshake", {
415
+ code: RPC_ERRORS.TIMEOUT,
416
+ reason: "HANDSHAKE_TIMEOUT"
417
+ })
418
+ );
419
+ }, opts.timeoutMs ?? 1e4);
420
+ const cleanup = () => {
421
+ off();
422
+ clearInterval(interval);
423
+ clearTimeout(timeout);
424
+ };
425
+ endpoint.notify(EVENT_READY);
426
+ });
427
+ }
428
+
429
+ // src/web/common.js
430
+ var CONVERSIONS = {
431
+ none: { label: "Raw value", units: "", fn: (v) => v },
432
+ "ms-kn": { label: "m/s \u2192 knots", units: "kn", fn: (v) => v * 1.943844 },
433
+ "ms-kmh": { label: "m/s \u2192 km/h", units: "km/h", fn: (v) => v * 3.6 },
434
+ "ms-mph": { label: "m/s \u2192 mph", units: "mph", fn: (v) => v * 2.236936 },
435
+ "k-c": { label: "K \u2192 \xB0C", units: "\xB0C", fn: (v) => v - 273.15 },
436
+ "k-f": {
437
+ label: "K \u2192 \xB0F",
438
+ units: "\xB0F",
439
+ fn: (v) => (v - 273.15) * 1.8 + 32
440
+ },
441
+ "rad-deg": {
442
+ label: "rad \u2192 \xB0",
443
+ units: "\xB0",
444
+ fn: (v) => v * 180 / Math.PI
445
+ },
446
+ "ratio-pct": { label: "ratio \u2192 %", units: "%", fn: (v) => v * 100 },
447
+ "m-ft": { label: "m \u2192 ft", units: "ft", fn: (v) => v * 3.28084 },
448
+ "m-nm": { label: "m \u2192 nm", units: "nm", fn: (v) => v / 1852 },
449
+ "m-km": { label: "m \u2192 km", units: "km", fn: (v) => v / 1e3 },
450
+ "pa-hpa": { label: "Pa \u2192 hPa", units: "hPa", fn: (v) => v / 100 }
451
+ };
452
+ function convert(value, conversionKey) {
453
+ const conv = CONVERSIONS[conversionKey] ?? CONVERSIONS.none;
454
+ return typeof value === "number" ? conv.fn(value) : value;
455
+ }
456
+ function formatValue(value, decimals = 1) {
457
+ if (typeof value !== "number" || !isFinite(value)) return "--";
458
+ return value.toFixed(decimals);
459
+ }
460
+ var LONG_PRESS_MS = 1500;
461
+ function installLongPress(client) {
462
+ let timer = null;
463
+ let fired = false;
464
+ const start = () => {
465
+ fired = false;
466
+ timer = setTimeout(() => {
467
+ fired = true;
468
+ client.call("ui.openConfigPanel").catch(() => {
469
+ });
470
+ }, LONG_PRESS_MS);
471
+ };
472
+ const cancel = () => {
473
+ if (timer) clearTimeout(timer);
474
+ timer = null;
475
+ };
476
+ window.addEventListener("pointerdown", start);
477
+ window.addEventListener("pointerup", cancel);
478
+ window.addEventListener("pointercancel", cancel);
479
+ window.addEventListener("pointerleave", cancel);
480
+ return () => fired;
481
+ }
482
+ async function startInstrument({ defaults = {}, onUpdate }) {
483
+ const client = await connectExtension();
484
+ const longPressFired = installLongPress(client);
485
+ let config = { ...defaults };
486
+ let value;
487
+ let unsubscribeSk = null;
488
+ const emit = () => onUpdate({ config, value, client });
489
+ async function applyConfig() {
490
+ const stored = await client.state.get();
491
+ config = { ...defaults, ...stored };
492
+ value = void 0;
493
+ if (unsubscribeSk) {
494
+ const u = unsubscribeSk;
495
+ unsubscribeSk = null;
496
+ await u().catch(() => {
497
+ });
498
+ }
499
+ emit();
500
+ if (config.path) {
501
+ unsubscribeSk = await client.signalk.subscribe([config.path], (ev) => {
502
+ value = ev.value;
503
+ emit();
504
+ });
505
+ }
506
+ }
507
+ await client.subscribe(["state.changed"], () => {
508
+ applyConfig().catch((err) => console.warn("config reload failed", err));
509
+ });
510
+ await applyConfig();
511
+ return { client, longPressFired };
512
+ }
513
+
514
+ // src/web/meter.js
515
+ function render({ config, value }) {
516
+ const root = document.getElementById("root");
517
+ const label = config.label || config.path || "Not configured";
518
+ const display = convert(value, config.convert ?? "ratio-pct");
519
+ const pct = typeof display === "number" && isFinite(display) ? Math.min(100, Math.max(0, display)) : 0;
520
+ root.innerHTML = `
521
+ <svg viewBox="0 0 200 100" preserveAspectRatio="xMidYMid meet">
522
+ <text x="100" y="18" class="label meter-label">${label}</text>
523
+ <rect x="8" y="28" width="184" height="32" rx="8" class="track"/>
524
+ <rect x="8" y="28" width="${184 * pct / 100}" height="32" rx="8" class="fillbar"/>
525
+ <text x="100" y="92" class="value meter-value">${formatValue(display, Number(config.decimals ?? 0))}%</text>
526
+ </svg>`;
527
+ }
528
+ startInstrument({
529
+ defaults: { convert: "ratio-pct", decimals: 0 },
530
+ onUpdate: render
531
+ }).catch((err) => {
532
+ document.getElementById("root").textContent = "Host connection failed";
533
+ console.error(err);
534
+ });
535
+ })();
536
+ //# sourceMappingURL=meter.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../signalk-plotterext-bus/src/protocol.ts", "../../../signalk-plotterext-bus/src/wildcard.ts", "../../../signalk-plotterext-bus/src/codec.ts", "../../../signalk-plotterext-bus/src/port.ts", "../../../signalk-plotterext-bus/src/endpoint.ts", "../../../signalk-plotterext-bus/src/extension.ts", "../../src/web/common.js", "../../src/web/meter.js"],
4
+ "sourcesContent": ["/**\n * Wire protocol types and constants for the Signal K plotter extension bus.\n *\n * The wire format is JSON-RPC 2.0 (https://www.jsonrpc.org/specification)\n * inside a routing envelope: `{ bus: \"plotterExt/1\", msg: <JSON-RPC object> }`.\n * Calls are JSON-RPC requests; events are JSON-RPC notifications whose\n * `method` is a hierarchical dot-separated event name.\n */\n\nexport const BUS_ID = 'plotterExt/1'\n\nexport type JsonRpcId = string | number\n\nexport interface JsonRpcRequest {\n jsonrpc: '2.0'\n id: JsonRpcId\n method: string\n params?: unknown\n}\n\nexport interface JsonRpcNotification {\n jsonrpc: '2.0'\n method: string\n params?: unknown\n}\n\nexport interface JsonRpcErrorObject {\n code: number\n message: string\n data?: { reason?: string; [key: string]: unknown }\n}\n\nexport interface JsonRpcSuccessResponse {\n jsonrpc: '2.0'\n id: JsonRpcId\n result: unknown\n}\n\nexport interface JsonRpcErrorResponse {\n jsonrpc: '2.0'\n id: JsonRpcId | null\n error: JsonRpcErrorObject\n}\n\nexport type JsonRpcResponse = JsonRpcSuccessResponse | JsonRpcErrorResponse\n\nexport type JsonRpcMessage =\n | JsonRpcRequest\n | JsonRpcNotification\n | JsonRpcResponse\n\nexport interface Envelope {\n bus: typeof BUS_ID\n msg: JsonRpcMessage\n}\n\n/**\n * JSON-RPC reserved codes for protocol errors, plus implementation-defined\n * codes (-32000..-32099 range) used by this package. Host API errors should\n * use HOST_ERROR with a stable string identifier in `error.data.reason`.\n */\nexport const RPC_ERRORS = {\n PARSE_ERROR: -32700,\n INVALID_REQUEST: -32600,\n METHOD_NOT_FOUND: -32601,\n INVALID_PARAMS: -32602,\n INTERNAL_ERROR: -32603,\n HOST_ERROR: -32000,\n TIMEOUT: -32001,\n CONNECTION_CLOSED: -32002\n} as const\n\nexport class RpcError extends Error {\n readonly code: number\n readonly data?: { reason?: string; [key: string]: unknown }\n\n constructor(\n message: string,\n opts: {\n code?: number\n reason?: string\n data?: Record<string, unknown>\n } = {}\n ) {\n super(message)\n this.name = 'RpcError'\n this.code = opts.code ?? RPC_ERRORS.HOST_ERROR\n const data: Record<string, unknown> = { ...(opts.data ?? {}) }\n if (opts.reason !== undefined) data.reason = opts.reason\n this.data = Object.keys(data).length > 0 ? data : undefined\n }\n\n get reason(): string | undefined {\n return typeof this.data?.reason === 'string' ? this.data.reason : undefined\n }\n\n toErrorObject(): JsonRpcErrorObject {\n return {\n code: this.code,\n message: this.message,\n ...(this.data ? { data: this.data } : {})\n }\n }\n\n static fromErrorObject(err: JsonRpcErrorObject): RpcError {\n return new RpcError(err.message, { code: err.code, data: err.data })\n }\n\n /** Normalize any thrown value into an RpcError suitable for the wire. */\n static from(err: unknown): RpcError {\n if (err instanceof RpcError) return err\n if (err instanceof Error) {\n return new RpcError(err.message, { code: RPC_ERRORS.INTERNAL_ERROR })\n }\n return new RpcError(String(err), { code: RPC_ERRORS.INTERNAL_ERROR })\n }\n}\n\n/** Reserved event names used to establish a connection. */\nexport const EVENT_READY = 'bus.ready'\nexport const EVENT_HANDSHAKE = 'bus.handshake'\n\nexport type ContextKind = 'panel' | 'widget' | 'background'\n\nexport interface HandshakeContext {\n kind: ContextKind\n /** Manifest-local contribution id. */\n id: string\n /** Host-assigned unique id for this placed instance (widgets). */\n instanceId?: string | null\n /** Widget instance a configuration panel was opened for. */\n targetInstance?: string | null\n /** Manifest-local widget id of the target instance (configuration panels). */\n targetWidget?: string | null\n}\n\nexport interface Handshake {\n host: string\n hostVersion: string\n apiVersion: string\n capabilities: string[]\n context: HandshakeContext\n}\n\n/** Payload of an `sk.<path>` Signal K value event. */\nexport interface SignalKValueEvent {\n path: string\n value: unknown\n timestamp?: string\n $source?: string\n}\n\nexport type StateScope = 'instance' | 'extension'\n\n/** Payload of a `state.changed` host event. */\nexport interface StateChangedEvent {\n scope: StateScope\n instanceId?: string | null\n keys: string[]\n}\n", "/**\n * eventemitter2-style wildcard matching for dot-separated event names.\n *\n * - `*` matches exactly one segment.\n * - `**` matches zero or more segments (any remainder when trailing).\n */\nexport function matchesPattern(pattern: string, name: string): boolean {\n if (pattern === name) return true\n return match(pattern.split('.'), 0, name.split('.'), 0)\n}\n\nfunction match(p: string[], pi: number, n: string[], ni: number): boolean {\n while (pi < p.length) {\n const seg = p[pi]\n if (seg === '**') {\n if (pi === p.length - 1) return true\n for (let skip = ni; skip <= n.length; skip++) {\n if (match(p, pi + 1, n, skip)) return true\n }\n return false\n }\n if (ni >= n.length) return false\n if (seg !== '*' && seg !== n[ni]) return false\n pi++\n ni++\n }\n return ni === n.length\n}\n\nexport function matchesAny(patterns: Iterable<string>, name: string): boolean {\n for (const pattern of patterns) {\n if (matchesPattern(pattern, name)) return true\n }\n return false\n}\n", "import {\n BUS_ID,\n Envelope,\n JsonRpcMessage,\n JsonRpcNotification,\n JsonRpcRequest,\n JsonRpcResponse\n} from './protocol'\n\nexport function wrap(msg: JsonRpcMessage): Envelope {\n return { bus: BUS_ID, msg }\n}\n\n/**\n * Returns the JSON-RPC message inside a bus envelope, or null when the data\n * is not valid protocol traffic (so unrelated postMessage noise is ignored).\n */\nexport function unwrap(data: unknown): JsonRpcMessage | null {\n if (typeof data !== 'object' || data === null) return null\n const env = data as Record<string, unknown>\n if (env.bus !== BUS_ID) return null\n return isJsonRpcMessage(env.msg) ? env.msg : null\n}\n\nexport function isJsonRpcMessage(v: unknown): v is JsonRpcMessage {\n if (typeof v !== 'object' || v === null) return false\n const m = v as Record<string, unknown>\n if (m.jsonrpc !== '2.0') return false\n if (typeof m.method === 'string') {\n // Request (with id) or notification (without).\n return (\n m.id === undefined ||\n typeof m.id === 'string' ||\n typeof m.id === 'number'\n )\n }\n // Response: id required (null allowed for unroutable errors), exactly one\n // of result / error.\n const idOk =\n typeof m.id === 'string' || typeof m.id === 'number' || m.id === null\n if (!idOk) return false\n const hasResult = 'result' in m\n const err = m.error as Record<string, unknown> | undefined\n const hasError =\n typeof err === 'object' &&\n err !== null &&\n typeof err.code === 'number' &&\n typeof err.message === 'string'\n return hasResult ? !('error' in m) : hasError\n}\n\nexport function isRequest(msg: JsonRpcMessage): msg is JsonRpcRequest {\n return 'method' in msg && 'id' in msg && msg.id !== undefined\n}\n\nexport function isNotification(\n msg: JsonRpcMessage\n): msg is JsonRpcNotification {\n return 'method' in msg && (!('id' in msg) || msg.id === undefined)\n}\n\nexport function isResponse(msg: JsonRpcMessage): msg is JsonRpcResponse {\n return !('method' in msg)\n}\n", "/**\n * Transport abstraction. The bus runs over anything that can post and\n * receive structured-cloneable data: window.postMessage between a host page\n * and an iframe, a MessageChannel port, or a test double.\n */\nexport interface BusPort {\n post(data: unknown): void\n /** Register a receive handler; returns an unlisten function. */\n listen(handler: (data: unknown) => void): () => void\n}\n\nexport interface WindowPortOptions {\n /**\n * targetOrigin for outgoing postMessage and required origin of incoming\n * messages. Defaults to the listening window's own origin (the baseline\n * deployment is same-origin). Pass '*' to disable origin checks.\n */\n origin?: string\n /** Window whose 'message' events are listened to. Defaults to globalThis. */\n listenWindow?: Window\n}\n\n/**\n * A port over window.postMessage to a peer window. On the host side the peer\n * is an iframe's contentWindow; on the extension side it is window.parent.\n * Only messages whose source is the peer (and whose origin matches) are\n * delivered.\n */\nexport function windowPort(peer: Window, opts: WindowPortOptions = {}): BusPort {\n const listenWindow =\n opts.listenWindow ?? (globalThis as unknown as Window)\n const origin = opts.origin ?? listenWindow.location?.origin ?? '*'\n return {\n post(data) {\n peer.postMessage(data, origin)\n },\n listen(handler) {\n const fn = (ev: MessageEvent) => {\n if (ev.source !== peer) return\n if (origin !== '*' && ev.origin !== origin) return\n handler(ev.data)\n }\n listenWindow.addEventListener('message', fn as EventListener)\n return () =>\n listenWindow.removeEventListener('message', fn as EventListener)\n }\n }\n}\n\ninterface MessagePortLike {\n postMessage(data: unknown): void\n addEventListener(type: 'message', fn: (ev: { data: unknown }) => void): void\n removeEventListener(\n type: 'message',\n fn: (ev: { data: unknown }) => void\n ): void\n start?(): void\n}\n\n/** A port over a MessageChannel/MessagePort (browser or Node). */\nexport function messagePort(port: MessagePortLike): BusPort {\n return {\n post(data) {\n port.postMessage(data)\n },\n listen(handler) {\n const fn = (ev: { data: unknown }) => handler(ev.data)\n port.addEventListener('message', fn)\n port.start?.()\n return () => port.removeEventListener('message', fn)\n }\n }\n}\n", "import {\n isNotification,\n isRequest,\n isResponse,\n unwrap,\n wrap\n} from './codec'\nimport {\n JsonRpcId,\n JsonRpcMessage,\n JsonRpcResponse,\n RPC_ERRORS,\n RpcError\n} from './protocol'\nimport { BusPort } from './port'\nimport { matchesAny } from './wildcard'\n\nexport interface MethodContext {\n endpoint: BusEndpoint\n}\n\nexport type MethodHandler = (\n params: unknown,\n ctx: MethodContext\n) => unknown | Promise<unknown>\n\nexport type EventHandler = (name: string, params: unknown) => void\n\nexport interface BusEndpointOptions {\n port: BusPort\n /** Default timeout for outgoing calls. */\n callTimeoutMs?: number\n /** Reporter for handler/dispatch errors. Defaults to console.warn. */\n onError?: (err: unknown) => void\n}\n\ninterface PendingCall {\n resolve: (value: unknown) => void\n reject: (err: RpcError) => void\n timer: ReturnType<typeof setTimeout> | null\n}\n\nconst DEFAULT_CALL_TIMEOUT_MS = 10_000\n\n/**\n * One end of the bus: sends/receives envelopes over a BusPort, dispatches\n * incoming requests to registered methods, correlates responses to pending\n * calls by per-call nonce, and fans incoming notifications out to wildcard\n * event handlers. Symmetric — both host and extension build on this.\n */\nexport class BusEndpoint {\n readonly callTimeoutMs: number\n\n private readonly port: BusPort\n private readonly unlisten: () => void\n private readonly onError: (err: unknown) => void\n private readonly pending = new Map<JsonRpcId, PendingCall>()\n private readonly methods = new Map<string, MethodHandler>()\n private readonly eventHandlers = new Set<{\n patterns: string[]\n fn: EventHandler\n }>()\n private readonly idPrefix = Math.random().toString(36).slice(2, 8)\n private seq = 0\n private closed = false\n\n constructor(opts: BusEndpointOptions) {\n this.port = opts.port\n this.callTimeoutMs = opts.callTimeoutMs ?? DEFAULT_CALL_TIMEOUT_MS\n this.onError =\n opts.onError ??\n ((err) => console.warn('[plotterext-bus]', err))\n this.unlisten = this.port.listen((data) => this.onData(data))\n }\n\n registerMethod(name: string, handler: MethodHandler): void {\n this.methods.set(name, handler)\n }\n\n unregisterMethod(name: string): void {\n this.methods.delete(name)\n }\n\n /**\n * Handle incoming notifications whose names match any of the wildcard\n * patterns. Returns an unsubscribe function. This is local dispatch only;\n * telling the peer which events to forward is a separate concern\n * (`events.subscribe`).\n */\n onEvent(patterns: string[], fn: EventHandler): () => void {\n const entry = { patterns, fn }\n this.eventHandlers.add(entry)\n return () => this.eventHandlers.delete(entry)\n }\n\n /** Send a notification (an event) to the peer. */\n notify(method: string, params?: unknown): void {\n this.send({ jsonrpc: '2.0', method, ...(params !== undefined ? { params } : {}) })\n }\n\n /** Call a method on the peer; resolves with its result. */\n call(\n method: string,\n params?: unknown,\n opts: { timeoutMs?: number } = {}\n ): Promise<unknown> {\n if (this.closed) {\n return Promise.reject(\n new RpcError('Bus endpoint is closed', {\n code: RPC_ERRORS.CONNECTION_CLOSED,\n reason: 'CLOSED'\n })\n )\n }\n const id = `${this.idPrefix}-${++this.seq}`\n const timeoutMs = opts.timeoutMs ?? this.callTimeoutMs\n return new Promise<unknown>((resolve, reject) => {\n const timer =\n timeoutMs > 0\n ? setTimeout(() => {\n this.pending.delete(id)\n reject(\n new RpcError(`Call timed out after ${timeoutMs}ms: ${method}`, {\n code: RPC_ERRORS.TIMEOUT,\n reason: 'TIMEOUT'\n })\n )\n }, timeoutMs)\n : null\n this.pending.set(id, { resolve, reject, timer })\n this.send({\n jsonrpc: '2.0',\n id,\n method,\n ...(params !== undefined ? { params } : {})\n })\n })\n }\n\n close(): void {\n if (this.closed) return\n this.closed = true\n this.unlisten()\n for (const [, p] of this.pending) {\n if (p.timer) clearTimeout(p.timer)\n p.reject(\n new RpcError('Bus endpoint closed', {\n code: RPC_ERRORS.CONNECTION_CLOSED,\n reason: 'CLOSED'\n })\n )\n }\n this.pending.clear()\n this.eventHandlers.clear()\n }\n\n private send(msg: JsonRpcMessage): void {\n if (this.closed) return\n this.port.post(wrap(msg))\n }\n\n private onData(data: unknown): void {\n const msg = unwrap(data)\n if (!msg) return\n if (isResponse(msg)) {\n this.onResponse(msg)\n } else if (isRequest(msg)) {\n void this.onRequest(msg)\n } else if (isNotification(msg)) {\n this.onNotification(msg.method, msg.params)\n }\n }\n\n private onResponse(msg: JsonRpcResponse): void {\n if (msg.id === null) return\n const p = this.pending.get(msg.id)\n if (!p) return\n this.pending.delete(msg.id)\n if (p.timer) clearTimeout(p.timer)\n if ('error' in msg) {\n p.reject(RpcError.fromErrorObject(msg.error))\n } else {\n p.resolve(msg.result)\n }\n }\n\n private async onRequest(msg: {\n id: JsonRpcId\n method: string\n params?: unknown\n }): Promise<void> {\n const handler = this.methods.get(msg.method)\n if (!handler) {\n this.send({\n jsonrpc: '2.0',\n id: msg.id,\n error: {\n code: RPC_ERRORS.METHOD_NOT_FOUND,\n message: `Method not found: ${msg.method}`\n }\n })\n return\n }\n try {\n const result = await handler(msg.params, { endpoint: this })\n this.send({\n jsonrpc: '2.0',\n id: msg.id,\n result: result === undefined ? null : result\n })\n } catch (err) {\n this.send({\n jsonrpc: '2.0',\n id: msg.id,\n error: RpcError.from(err).toErrorObject()\n })\n }\n }\n\n private onNotification(name: string, params: unknown): void {\n for (const entry of [...this.eventHandlers]) {\n if (matchesAny(entry.patterns, name)) {\n try {\n entry.fn(name, params)\n } catch (err) {\n this.onError(err)\n }\n }\n }\n }\n}\n", "import { BusEndpoint, EventHandler } from './endpoint'\nimport { BusPort, windowPort } from './port'\nimport {\n EVENT_HANDSHAKE,\n EVENT_READY,\n Handshake,\n HandshakeContext,\n RPC_ERRORS,\n RpcError,\n SignalKValueEvent,\n StateScope\n} from './protocol'\n\nexport interface ConnectOptions {\n /** Transport. Defaults to window.postMessage to window.parent. */\n port?: BusPort\n /** Interval for re-sending bus.ready until the handshake arrives. */\n readyIntervalMs?: number\n /** How long to wait for the host handshake before rejecting. */\n timeoutMs?: number\n /** Default timeout for host API calls. */\n callTimeoutMs?: number\n onError?: (err: unknown) => void\n}\n\nexport type Unsubscribe = () => Promise<void>\n\n/**\n * The extension side of the bus: one per iframe context (panel, widget, or\n * background runtime). Create via connectExtension().\n */\nexport class ExtensionClient {\n readonly handshake: Handshake\n readonly endpoint: BusEndpoint\n\n constructor(endpoint: BusEndpoint, handshake: Handshake) {\n this.endpoint = endpoint\n this.handshake = handshake\n }\n\n get context(): HandshakeContext {\n return this.handshake.context\n }\n\n get apiVersion(): string {\n return this.handshake.apiVersion\n }\n\n get capabilities(): string[] {\n return this.handshake.capabilities\n }\n\n hasCapability(id: string): boolean {\n return this.handshake.capabilities.includes(id)\n }\n\n /** Call a host API method. */\n call(\n method: string,\n params?: unknown,\n opts?: { timeoutMs?: number }\n ): Promise<unknown> {\n return this.endpoint.call(method, params, opts)\n }\n\n /** Send a notification to the host. */\n notify(method: string, params?: unknown): void {\n this.endpoint.notify(method, params)\n }\n\n /**\n * Subscribe to host events matching wildcard patterns. Registers both the\n * host-side forwarding subscription and local dispatch; the returned\n * function tears down both.\n */\n async subscribe(\n patterns: string[],\n handler: EventHandler\n ): Promise<Unsubscribe> {\n const off = this.endpoint.onEvent(patterns, handler)\n let subscriptionId: string\n try {\n const result = (await this.call('events.subscribe', { patterns })) as {\n subscriptionId: string\n }\n subscriptionId = result.subscriptionId\n } catch (err) {\n off()\n throw err\n }\n return async () => {\n off()\n await this.call('events.unsubscribe', { subscriptionId }).catch(() => {\n // Best-effort: the host may already have dropped the connection.\n })\n }\n }\n\n /** Host-persisted key/value state (see spec: State Storage). */\n readonly state = {\n get: async (\n keys?: string[],\n scope?: StateScope\n ): Promise<Record<string, unknown>> => {\n const result = (await this.call('state.get', {\n ...(scope ? { scope } : {}),\n ...(keys ? { keys } : {})\n })) as { values: Record<string, unknown> }\n return result.values ?? {}\n },\n set: async (\n values: Record<string, unknown>,\n scope?: StateScope\n ): Promise<void> => {\n await this.call('state.set', {\n ...(scope ? { scope } : {}),\n values\n })\n }\n }\n\n /** Signal K data relayed by the host (capabilities signalk.stream / .put). */\n readonly signalk = {\n /**\n * Subscribe to Signal K path values. The host publishes them as\n * `sk.<path>` events; this helper hides the event-name mapping and\n * establishes both the event-forwarding subscription and the host's\n * upstream Signal K subscription.\n */\n subscribe: async (\n paths: string[],\n handler: (ev: SignalKValueEvent) => void\n ): Promise<Unsubscribe> => {\n const patterns = paths.map((p) => `sk.${p}`)\n const offEvents = await this.subscribe(patterns, (_name, params) =>\n handler(params as SignalKValueEvent)\n )\n let subscriptionId: string\n try {\n const result = (await this.call('signalk.subscribe', { paths })) as {\n subscriptionId: string\n }\n subscriptionId = result.subscriptionId\n } catch (err) {\n await offEvents()\n throw err\n }\n return async () => {\n await offEvents()\n await this.call('signalk.unsubscribe', { subscriptionId }).catch(\n () => {}\n )\n }\n },\n put: (path: string, value: unknown): Promise<unknown> => {\n return this.call('signalk.put', { path, value })\n }\n }\n\n close(): void {\n this.endpoint.close()\n }\n}\n\n/**\n * Connect to the host from inside an extension iframe. Sends `bus.ready`\n * (repeating until answered) and resolves once the host's `bus.handshake`\n * arrives.\n */\nexport function connectExtension(\n opts: ConnectOptions = {}\n): Promise<ExtensionClient> {\n // Default transport: postMessage to the embedding window. Origin checks\n // are relaxed to '*' on the extension side because the host page's origin\n // may legitimately differ from the extension asset origin (e.g. a host dev\n // server embedding extension assets served by the Signal K server); the\n // peer-source check in windowPort still applies, and the host side\n // enforces a strict origin for the extension's frame.\n const port =\n opts.port ??\n windowPort((globalThis as unknown as Window).parent as Window, {\n origin: '*'\n })\n const endpoint = new BusEndpoint({\n port,\n callTimeoutMs: opts.callTimeoutMs,\n onError: opts.onError\n })\n return new Promise<ExtensionClient>((resolve, reject) => {\n let done = false\n const off = endpoint.onEvent([EVENT_HANDSHAKE], (_name, params) => {\n if (done) return\n done = true\n cleanup()\n resolve(new ExtensionClient(endpoint, params as Handshake))\n })\n const interval = setInterval(\n () => endpoint.notify(EVENT_READY),\n opts.readyIntervalMs ?? 250\n )\n const timeout = setTimeout(() => {\n if (done) return\n done = true\n cleanup()\n endpoint.close()\n reject(\n new RpcError('Timed out waiting for host handshake', {\n code: RPC_ERRORS.TIMEOUT,\n reason: 'HANDSHAKE_TIMEOUT'\n })\n )\n }, opts.timeoutMs ?? 10_000)\n const cleanup = () => {\n off()\n clearInterval(interval)\n clearTimeout(timeout)\n }\n endpoint.notify(EVENT_READY)\n })\n}\n\nexport * from './protocol'\nexport * from './wildcard'\nexport { BusEndpoint } from './endpoint'\nexport type { MethodHandler, MethodContext, EventHandler } from './endpoint'\nexport * from './port'\n", "// Shared runtime for instrument widgets: host connection, per-instance\n// configuration, Signal K value subscription, unit conversion, and the\n// press-and-hold gesture that asks the host to open the configuration panel.\n// (Pointer events inside a sandboxed iframe are invisible to the host, so the\n// gesture is detected here and delivered via the ui.openConfigPanel method.)\n\nimport { connectExtension } from 'signalk-plotterext-bus/extension'\n\nexport const CONVERSIONS = {\n none: { label: 'Raw value', units: '', fn: (v) => v },\n 'ms-kn': { label: 'm/s \u2192 knots', units: 'kn', fn: (v) => v * 1.943844 },\n 'ms-kmh': { label: 'm/s \u2192 km/h', units: 'km/h', fn: (v) => v * 3.6 },\n 'ms-mph': { label: 'm/s \u2192 mph', units: 'mph', fn: (v) => v * 2.236936 },\n 'k-c': { label: 'K \u2192 \u00B0C', units: '\u00B0C', fn: (v) => v - 273.15 },\n 'k-f': {\n label: 'K \u2192 \u00B0F',\n units: '\u00B0F',\n fn: (v) => (v - 273.15) * 1.8 + 32\n },\n 'rad-deg': {\n label: 'rad \u2192 \u00B0',\n units: '\u00B0',\n fn: (v) => (v * 180) / Math.PI\n },\n 'ratio-pct': { label: 'ratio \u2192 %', units: '%', fn: (v) => v * 100 },\n 'm-ft': { label: 'm \u2192 ft', units: 'ft', fn: (v) => v * 3.28084 },\n 'm-nm': { label: 'm \u2192 nm', units: 'nm', fn: (v) => v / 1852 },\n 'm-km': { label: 'm \u2192 km', units: 'km', fn: (v) => v / 1000 },\n 'pa-hpa': { label: 'Pa \u2192 hPa', units: 'hPa', fn: (v) => v / 100 }\n}\n\nexport function convert(value, conversionKey) {\n const conv = CONVERSIONS[conversionKey] ?? CONVERSIONS.none\n return typeof value === 'number' ? conv.fn(value) : value\n}\n\nexport function conversionUnits(conversionKey) {\n return (CONVERSIONS[conversionKey] ?? CONVERSIONS.none).units\n}\n\nexport function formatValue(value, decimals = 1) {\n if (typeof value !== 'number' || !isFinite(value)) return '--'\n return value.toFixed(decimals)\n}\n\nconst LONG_PRESS_MS = 1500\n\nfunction installLongPress(client) {\n let timer = null\n let fired = false\n const start = () => {\n fired = false\n timer = setTimeout(() => {\n fired = true\n client.call('ui.openConfigPanel').catch(() => {})\n }, LONG_PRESS_MS)\n }\n const cancel = () => {\n if (timer) clearTimeout(timer)\n timer = null\n }\n window.addEventListener('pointerdown', start)\n window.addEventListener('pointerup', cancel)\n window.addEventListener('pointercancel', cancel)\n window.addEventListener('pointerleave', cancel)\n return () => fired\n}\n\n/**\n * Connect, load per-instance config, follow config changes and the\n * configured Signal K path. Calls onUpdate({ config, value, client }) on\n * every change. Returns { client, longPressFired } once connected.\n */\nexport async function startInstrument({ defaults = {}, onUpdate }) {\n const client = await connectExtension()\n const longPressFired = installLongPress(client)\n\n let config = { ...defaults }\n let value\n let unsubscribeSk = null\n\n const emit = () => onUpdate({ config, value, client })\n\n async function applyConfig() {\n const stored = await client.state.get()\n config = { ...defaults, ...stored }\n value = undefined\n if (unsubscribeSk) {\n const u = unsubscribeSk\n unsubscribeSk = null\n await u().catch(() => {})\n }\n emit()\n if (config.path) {\n unsubscribeSk = await client.signalk.subscribe([config.path], (ev) => {\n value = ev.value\n emit()\n })\n }\n }\n\n await client.subscribe(['state.changed'], () => {\n applyConfig().catch((err) => console.warn('config reload failed', err))\n })\n await applyConfig()\n return { client, longPressFired }\n}\n", "// Meter widget: a horizontal 0-100% bar for ratio-style Signal K paths.\n\nimport {\n startInstrument,\n convert,\n formatValue\n} from './common.js'\n\nfunction render({ config, value }) {\n const root = document.getElementById('root')\n const label = config.label || config.path || 'Not configured'\n // A meter shows percent: ratio paths (0..1) use the ratio->% conversion by\n // default; paths already in percent can use 'none'.\n const display = convert(value, config.convert ?? 'ratio-pct')\n const pct =\n typeof display === 'number' && isFinite(display)\n ? Math.min(100, Math.max(0, display))\n : 0\n\n root.innerHTML = `\n <svg viewBox=\"0 0 200 100\" preserveAspectRatio=\"xMidYMid meet\">\n <text x=\"100\" y=\"18\" class=\"label meter-label\">${label}</text>\n <rect x=\"8\" y=\"28\" width=\"184\" height=\"32\" rx=\"8\" class=\"track\"/>\n <rect x=\"8\" y=\"28\" width=\"${(184 * pct) / 100}\" height=\"32\" rx=\"8\" class=\"fillbar\"/>\n <text x=\"100\" y=\"92\" class=\"value meter-value\">${formatValue(display, Number(config.decimals ?? 0))}%</text>\n </svg>`\n}\n\nstartInstrument({\n defaults: { convert: 'ratio-pct', decimals: 0 },\n onUpdate: render\n}).catch((err) => {\n document.getElementById('root').textContent = 'Host connection failed'\n console.error(err)\n})\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }