@camstack/addon-osd-manager 0.1.157 → 0.1.161

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.
Files changed (17) hide show
  1. package/dist/{MotionZonesSettings-vYt4dibj.mjs → MotionZonesSettings-CSheC3_V.mjs} +2 -2
  2. package/dist/{PrivacyMaskSettings-BiQ78hst.mjs → PrivacyMaskSettings-I6eE4GFM.mjs} +4 -4
  3. package/dist/{SceneMonitorEditor-CJtJIyMf.mjs → SceneMonitorEditor-mNJ89n0T.mjs} +3 -3
  4. package/dist/_stub.js +3059 -2713
  5. package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_osd_manager_page-DdAtpC7z.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_osd_manager_page-BYQUZdGL.mjs} +2 -2
  6. package/dist/{_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BtgV-Ym5.mjs → _virtual_mf___mfe_internal__addon_osd_manager_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-B2mEdhqd.mjs} +1 -1
  7. package/dist/{hostInit-0Mc7Fm1R.mjs → hostInit-DFyjx8Xk.mjs} +1 -1
  8. package/dist/index.js +350 -57
  9. package/dist/index.mjs +322 -58
  10. package/dist/{player-overlays-BPyna-eI.mjs → player-overlays-DFXmMB9Y.mjs} +1 -1
  11. package/dist/remoteEntry.js +1 -1
  12. package/dist/{responsive-B0yDP9vf.mjs → responsive-DLFs-5i_.mjs} +1 -1
  13. package/dist/{scene-monitor-copy-BzX20O6q.mjs → scene-monitor-copy-DVNcikwa.mjs} +1 -1
  14. package/dist/{square-BOMOn_6j.mjs → square-Co3Eglp3.mjs} +1 -1
  15. package/dist/{trash-2-BolGRl2P.mjs → trash-2-DnKldIWK.mjs} +1 -1
  16. package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_osd_manager_page__remoteEntry_js-UY9P_4V2.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_osd_manager_page__remoteEntry_js-C3yC0JaQ.mjs} +1 -1
  17. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -39327,22 +39327,35 @@ setOverlay: method(object({
39327
39327
  }
39328
39328
  }] }
39329
39329
  };
39330
+ _enum([
39331
+ "value",
39332
+ "unit",
39333
+ "full"
39334
+ ]);
39335
+ _enum([
39336
+ "value+unit",
39337
+ "value",
39338
+ "unit"
39339
+ ]);
39330
39340
  /**
39331
- * `osd-manager` — the ORCHESTRATOR over the device-scope `osd` cap.
39332
- *
39333
- * The `osd` cap is the firmware contract: it probes a camera's overlay
39334
- * SLOTS and writes literal text into one. It has no idea WHERE that text
39335
- * comes from, and it must not — a driver that grew a "show the temperature
39336
- * here" feature would grow it once per vendor.
39337
- *
39338
- * This cap owns the other half: a per-(camera, slot) BINDING that says
39339
- * which value feeds the slot, how it is formatted, and under which
39340
- * conditions it is shown at all. Named TEMPLATES combine many device
39341
- * fields into one overlay string (Scrypted OSD Manager parity).
39341
+ * How the operator reached the leaf device. `deviceId` is ALWAYS the
39342
+ * OSD-supported leaf that is read; `containerId` is set when the pick
39343
+ * went through a container (gateway) first.
39342
39344
  */
39343
- /** One field reference inside a named multi-device template. */
39344
- var OsdTemplateFieldSchema = object({
39345
- deviceId: number().int(),
39345
+ var OsdTemplatePickSchema = object({
39346
+ containerId: number().int().optional(),
39347
+ deviceId: number().int()
39348
+ });
39349
+ /**
39350
+ * One named binding inside a multi-device template: device path + unit
39351
+ * selection + precision. The parser expands `{alias}`, `{alias.value}`,
39352
+ * `{alias.unit}`, or `{alias.full}` from the same binding — there is no
39353
+ * per-binding format lock.
39354
+ */
39355
+ var OsdTemplateBindingSchema = object({
39356
+ /** Token stem in the parser, e.g. `t` → `{t}` / `{t.value}`. Unique within the template. */
39357
+ alias: string().min(1).max(32).regex(/^[A-Za-z][A-Za-z0-9_-]*$/, "alias must start with a letter"),
39358
+ pick: OsdTemplatePickSchema,
39346
39359
  capName: string().min(1).max(64),
39347
39360
  valuePath: string().min(1).max(64),
39348
39361
  /** Convert TO this unit at render (`tryConvertUnit`). Absent = source unit. */
@@ -39350,14 +39363,14 @@ var OsdTemplateFieldSchema = object({
39350
39363
  maxDecimals: number().int().min(0).max(4).default(1)
39351
39364
  });
39352
39365
  /**
39353
- * A named template: several device fields → one parser string with
39354
- * `{deviceId.capName.valuePath}` tokens (Scrypted `{deviceId.sensorId}`).
39366
+ * A named template: bindings (alias → leaf field) + a parser that
39367
+ * references `{alias}` / `{alias.value}` / `{alias.unit}` / `{alias.full}`.
39355
39368
  */
39356
39369
  var OsdNamedTemplateSchema = object({
39357
39370
  id: string().min(1).max(64),
39358
39371
  name: string().min(1).max(64),
39359
- fields: array(OsdTemplateFieldSchema).max(32).default([]),
39360
- /** Parser with multi-token placeholders, e.g. `T {12.thermometer.value}°`. */
39372
+ bindings: array(OsdTemplateBindingSchema).max(32).default([]),
39373
+ /** Parser with alias placeholders, e.g. `{t.value} {t.unit} / {h}`. */
39361
39374
  parser: string().max(256).default("")
39362
39375
  });
39363
39376
  /** Where a slot's value comes from. */
@@ -45563,6 +45576,10 @@ var UNIT_TABLE = {
45563
45576
  function specFor(unit) {
45564
45577
  return Object.hasOwn(UNIT_TABLE, unit) ? UNIT_TABLE[unit] : void 0;
45565
45578
  }
45579
+ /** The dimension a unit belongs to, or `undefined` for an unknown unit. */
45580
+ function unitDimension(unit) {
45581
+ return specFor(unit)?.dimension;
45582
+ }
45566
45583
  /** Value in `spec`'s unit → its dimension's canonical unit. */
45567
45584
  function toCanonical(value, spec) {
45568
45585
  return value * spec.factor + spec.offset;
@@ -58364,14 +58381,91 @@ function readStatePath(slice, path) {
58364
58381
  }
58365
58382
  //#endregion
58366
58383
  //#region src/template-tokens.ts
58367
- /** Build the placeholder token for one template field. */
58368
- function fieldToken(field) {
58369
- return `{${field.deviceId}.${field.capName}.${field.valuePath}}`;
58384
+ /** Build a placeholder token for one binding alias + expand kind. */
58385
+ function aliasToken(alias, kind = "full") {
58386
+ if (kind === "full") return `{${alias}}`;
58387
+ return `{${alias}.${kind}}`;
58388
+ }
58389
+ /** Explicit `{alias.full}` form (chips prefer this over bare `{alias}`). */
58390
+ function aliasFullToken(alias) {
58391
+ return `{${alias}.full}`;
58392
+ }
58393
+ /** Chip / parser insert options for one binding (value, unit, value+unit). */
58394
+ function bindingInsertChips(binding) {
58395
+ const alias = binding.alias;
58396
+ return [
58397
+ {
58398
+ id: `${alias}.value`,
58399
+ label: `${alias} · value`,
58400
+ token: aliasToken(alias, "value"),
58401
+ kind: "value"
58402
+ },
58403
+ {
58404
+ id: `${alias}.unit`,
58405
+ label: `${alias} · unit`,
58406
+ token: aliasToken(alias, "unit"),
58407
+ kind: "unit"
58408
+ },
58409
+ {
58410
+ id: `${alias}.full`,
58411
+ label: `${alias} · value+unit`,
58412
+ token: aliasFullToken(alias),
58413
+ kind: "full"
58414
+ }
58415
+ ];
58416
+ }
58417
+ /** @deprecated Prefer `bindingInsertChips` — bare `{alias}` only. */
58418
+ function bindingToken(binding) {
58419
+ return aliasToken(binding.alias, "full");
58420
+ }
58421
+ /**
58422
+ * Parse `{alias}`, `{alias.value}`, `{alias.unit}`, `{alias.full}` from a
58423
+ * parser string. Bare `{alias}` and `{alias.full}` both mean `full`.
58424
+ */
58425
+ function parseAliasTokens(parser) {
58426
+ const out = [];
58427
+ const seen = /* @__PURE__ */ new Set();
58428
+ for (const match of parser.matchAll(/\{([A-Za-z][A-Za-z0-9_-]*)(?:\.(value|unit|full))?\}/g)) {
58429
+ const alias = match[1];
58430
+ if (alias === void 0 || alias === "") continue;
58431
+ const suffix = match[2];
58432
+ const kind = suffix === "value" || suffix === "unit" ? suffix : "full";
58433
+ const token = match[0] ?? "";
58434
+ const key = `${alias}:${kind}:${token}`;
58435
+ if (seen.has(key)) continue;
58436
+ seen.add(key);
58437
+ out.push({
58438
+ alias,
58439
+ kind,
58440
+ token
58441
+ });
58442
+ }
58443
+ return out;
58444
+ }
58445
+ /** Unique alias stems referenced by a parser (any token kind). */
58446
+ function parseAliasStems(parser) {
58447
+ const stems = [];
58448
+ for (const entry of parseAliasTokens(parser)) if (!stems.includes(entry.alias)) stems.push(entry.alias);
58449
+ return stems;
58370
58450
  }
58371
58451
  /** Replace every occurrence of `token` in `parser` with `value`. */
58372
58452
  function replaceToken(parser, token, value) {
58373
58453
  return parser.replaceAll(token, value);
58374
58454
  }
58455
+ /**
58456
+ * Suggest a short unique alias from a device/field name.
58457
+ * Falls back to `b1`, `b2`, … when the slug collides.
58458
+ */
58459
+ function suggestAlias(seed, taken, fallbackPrefix = "b") {
58460
+ const slug = seed.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").replace(/^(\d)/, "a$1").slice(0, 24);
58461
+ const base = slug.length > 0 ? slug : fallbackPrefix;
58462
+ if (!taken.has(base)) return base;
58463
+ for (let i = 2; i < 1e3; i += 1) {
58464
+ const candidate = `${base}${i}`.slice(0, 32);
58465
+ if (!taken.has(candidate)) return candidate;
58466
+ }
58467
+ return `${fallbackPrefix}${Date.now().toString(36)}`;
58468
+ }
58375
58469
  //#endregion
58376
58470
  //#region src/unit-display.ts
58377
58471
  /**
@@ -58379,6 +58473,16 @@ function replaceToken(parser, token, value) {
58379
58473
  * UNIT_TABLE / tryConvertUnit (same set as D699).
58380
58474
  */
58381
58475
  /**
58476
+ * Dimension for a UnitPicker from a raw source unit (may be bare `C`).
58477
+ * Normalize before `unitDimension` — the table only knows canonical spellings.
58478
+ */
58479
+ function dimensionFromSourceUnit(sourceUnit) {
58480
+ if (sourceUnit === void 0) return void 0;
58481
+ const normalized = normalizeUnit(sourceUnit);
58482
+ if (normalized === void 0) return void 0;
58483
+ return unitDimension(normalized);
58484
+ }
58485
+ /**
58382
58486
  * Convert `raw` from `sourceUnit` to `displayUnit` when both are known and
58383
58487
  * same-dimension. On refusal, keep the raw value and the source unit so the
58384
58488
  * label never lies about the number.
@@ -58427,24 +58531,64 @@ function inferSourceUnit(slice, valuePath) {
58427
58531
  }
58428
58532
  //#endregion
58429
58533
  //#region src/named-template-render.ts
58534
+ /** Combine value/unit for a token kind. */
58535
+ function applyTemplateTokenKind(value, unit, kind) {
58536
+ if (kind === "value") return value;
58537
+ if (kind === "unit") return unit;
58538
+ const v = value.trim();
58539
+ const u = unit.trim();
58540
+ if (v === "") return u;
58541
+ if (u === "") return v;
58542
+ return `${v} ${u}`;
58543
+ }
58544
+ /**
58545
+ * @deprecated Prefer `applyTemplateTokenKind`. Maps legacy format enum to
58546
+ * the same join rules (`value+unit` → `full`).
58547
+ */
58548
+ function applyTemplateFormat(value, unit, format) {
58549
+ return applyTemplateTokenKind(value, unit, format === "value" ? "value" : format === "unit" ? "unit" : "full");
58550
+ }
58551
+ /** Resolve one binding to live value/unit/formatted parts (or a reason). */
58552
+ function resolveBindingLive(binding, facts) {
58553
+ const deviceId = binding.pick.deviceId;
58554
+ const deviceState = facts.stateByDevice.get(deviceId);
58555
+ if (deviceState === void 0) return { reason: `no state mirrored for device ${deviceId}` };
58556
+ const slice = deviceState[binding.capName];
58557
+ if (slice === void 0) return { reason: `device ${deviceId} publishes no "${binding.capName}" state` };
58558
+ const raw = readStatePath(slice, binding.valuePath);
58559
+ if (raw === void 0) return { reason: `"${binding.capName}.${binding.valuePath}" is absent on device ${deviceId}` };
58560
+ let value;
58561
+ let unit = "";
58562
+ if (typeof raw === "number") {
58563
+ const converted = convertForDisplay(raw, inferSourceUnit(slice, binding.valuePath), binding.displayUnit);
58564
+ value = truncateDecimals(converted.value, binding.maxDecimals);
58565
+ unit = converted.unit;
58566
+ } else if (typeof raw === "boolean") value = raw ? "ON" : "OFF";
58567
+ else if (raw === null) value = "";
58568
+ else value = String(raw);
58569
+ return {
58570
+ value,
58571
+ unit,
58572
+ formatted: applyTemplateTokenKind(value, unit, "full")
58573
+ };
58574
+ }
58575
+ /** Substitute every token form for one alias from live parts. Longest first. */
58576
+ function expandBindingTokens(parser, alias, live) {
58577
+ let text = parser;
58578
+ text = replaceToken(text, aliasFullToken(alias), live.formatted);
58579
+ text = replaceToken(text, aliasToken(alias, "value"), live.value);
58580
+ text = replaceToken(text, aliasToken(alias, "unit"), live.unit);
58581
+ text = replaceToken(text, aliasToken(alias, "full"), live.formatted);
58582
+ return text;
58583
+ }
58430
58584
  /** Render one named template's parser from live states. */
58431
58585
  function renderNamedTemplate(template, facts) {
58432
58586
  let text = template.parser;
58433
- if (template.fields.length === 0) return text.trim() === "" ? { reason: `template "${template.id}" has no fields` } : { text };
58434
- for (const field of template.fields) {
58435
- const token = fieldToken(field);
58436
- const deviceState = facts.stateByDevice.get(field.deviceId);
58437
- if (deviceState === void 0) return { reason: `no state mirrored for device ${field.deviceId}` };
58438
- const slice = deviceState[field.capName];
58439
- if (slice === void 0) return { reason: `device ${field.deviceId} publishes no "${field.capName}" state` };
58440
- const raw = readStatePath(slice, field.valuePath);
58441
- if (raw === void 0) return { reason: `"${field.capName}.${field.valuePath}" is absent on device ${field.deviceId}` };
58442
- let rendered;
58443
- if (typeof raw === "number") rendered = truncateDecimals(convertForDisplay(raw, inferSourceUnit(slice, field.valuePath), field.displayUnit).value, field.maxDecimals);
58444
- else if (typeof raw === "boolean") rendered = raw ? "ON" : "OFF";
58445
- else if (raw === null) rendered = "";
58446
- else rendered = String(raw);
58447
- text = replaceToken(text, token, rendered);
58587
+ if (template.bindings.length === 0) return text.trim() === "" ? { reason: `template "${template.id}" has no bindings` } : { text };
58588
+ for (const binding of template.bindings) {
58589
+ const live = resolveBindingLive(binding, facts);
58590
+ if ("reason" in live) return live;
58591
+ text = expandBindingTokens(text, binding.alias, live);
58448
58592
  }
58449
58593
  return { text };
58450
58594
  }
@@ -58546,20 +58690,98 @@ function settle(slotId, text, previousText, outcome, reason) {
58546
58690
  };
58547
58691
  }
58548
58692
  //#endregion
58693
+ //#region src/osd-source-fields.ts
58694
+ /** Caps the OSD source picker may bind. */
58695
+ var OSD_SOURCE_CAP_NAMES = new Set([
58696
+ "temperature-sensor",
58697
+ "humidity-sensor",
58698
+ "climate-control",
58699
+ "numeric-sensor",
58700
+ "weather",
58701
+ "air-quality-sensor",
58702
+ "lock-control",
58703
+ "contact",
58704
+ "binary",
58705
+ "battery"
58706
+ ]);
58707
+ /**
58708
+ * Per-cap leaf allowlist. Absent entry ⇒ every leaf from `stateLeavesOf`
58709
+ * that survives bookkeeping (single-metric sensors). Present ⇒ only these
58710
+ * paths (climate / weather / battery).
58711
+ */
58712
+ var OSD_SOURCE_PATHS = new Map([
58713
+ ["climate-control", new Set([
58714
+ "currentTemp",
58715
+ "currentHumidity",
58716
+ "target",
58717
+ "targetHumidity",
58718
+ "mode"
58719
+ ])],
58720
+ ["weather", new Set([
58721
+ "condition",
58722
+ "temperature",
58723
+ "humidity",
58724
+ "pressure",
58725
+ "windSpeed",
58726
+ "windBearing"
58727
+ ])],
58728
+ ["battery", new Set(["percentage"])],
58729
+ ["temperature-sensor", new Set(["celsius"])],
58730
+ ["humidity-sensor", new Set(["percent"])],
58731
+ ["numeric-sensor", new Set(["value"])],
58732
+ ["air-quality-sensor", new Set([
58733
+ "co2Ppm",
58734
+ "vocPpb",
58735
+ "pm25",
58736
+ "pm10",
58737
+ "aqi"
58738
+ ])],
58739
+ ["binary", new Set(["on"])],
58740
+ ["contact", new Set(["entryOpen"])],
58741
+ ["lock-control", new Set(["state"])]
58742
+ ]);
58743
+ function isOsdSourceCap(capName) {
58744
+ return OSD_SOURCE_CAP_NAMES.has(capName);
58745
+ }
58746
+ /** Bindable OSD leaves for one cap — empty when the cap is not OSD-relevant. */
58747
+ function osdStateLeavesOf(cap) {
58748
+ if (!isOsdSourceCap(cap.name)) return [];
58749
+ const leaves = stateLeavesOf(cap);
58750
+ const allowed = OSD_SOURCE_PATHS.get(cap.name);
58751
+ if (allowed === void 0) return leaves;
58752
+ return leaves.filter((leaf) => allowed.has(leaf.path));
58753
+ }
58754
+ /** Device types that never contribute their OWN catalog leaves. */
58755
+ var OSD_NON_LEAF_TYPES = new Set(["container"]);
58756
+ /** True when this device type may expose OSD leaves (not a container gateway). */
58757
+ function isOsdLeafDeviceType(type) {
58758
+ if (type === null || type === void 0) return true;
58759
+ return !OSD_NON_LEAF_TYPES.has(type);
58760
+ }
58761
+ /** @deprecated Prefer {@link isOsdLeafDeviceType}. */
58762
+ var OSD_PICKER_EXCLUDED_TYPES = OSD_NON_LEAF_TYPES;
58763
+ /** @deprecated Prefer {@link isOsdLeafDeviceType}. */
58764
+ function isOsdPickerDeviceType(type) {
58765
+ return isOsdLeafDeviceType(type);
58766
+ }
58767
+ function isContainerDeviceType(type) {
58768
+ return type === "container";
58769
+ }
58770
+ //#endregion
58549
58771
  //#region src/source-catalog.ts
58550
58772
  /**
58551
- * Build the editor's source picker from the CAP DEFINITIONS — never a
58552
- * hand-written list. A cap that ships a new runtime-state field becomes
58553
- * bindable the moment it deploys, with no change here.
58773
+ * Build the editor's source picker from OSD-relevant cap definitions.
58774
+ * Gateway / infrastructure caps (broker, mqtt, deviceExport, …) never
58775
+ * appear — see `osd-source-fields.ts`.
58554
58776
  */
58555
58777
  /** Turn `air-quality-sensor` + `pm25` into `Air Quality Sensor · pm25`. */
58556
58778
  function humanizeCapName(capName) {
58557
58779
  return capName.split("-").map((part) => part.length === 0 ? part : part[0].toUpperCase() + part.slice(1)).join(" ");
58558
58780
  }
58559
58781
  /**
58560
- * Every bindable value across the cluster, with the camera's own state
58561
- * first — that is what an operator reaches for, and a picker that buries
58562
- * it under 200 other devices is a picker nobody uses.
58782
+ * Every OSD-bindable value across the cluster, with the camera's own
58783
+ * state first — that is what an operator reaches for, and a picker that
58784
+ * buries it under 200 other devices is a picker nobody uses.
58563
58785
  */
58564
58786
  function sourceOptionsFor(cameraDeviceId, devices, capsByDevice) {
58565
58787
  const capsByName = new Map(ALL_CAPABILITY_DEFINITIONS.map((cap) => [cap.name, cap]));
@@ -58569,18 +58791,22 @@ function sourceOptionsFor(cameraDeviceId, devices, capsByDevice) {
58569
58791
  return a.id - b.id;
58570
58792
  });
58571
58793
  const options = [];
58572
- for (const device of ordered) for (const capName of capsByDevice.get(device.id) ?? []) {
58573
- const cap = capsByName.get(capName);
58574
- if (cap === void 0) continue;
58575
- for (const leaf of stateLeavesOf(cap)) options.push({
58576
- deviceId: device.id,
58577
- deviceName: device.name,
58578
- capName,
58579
- valuePath: leaf.path,
58580
- label: `${humanizeCapName(capName)} · ${leaf.path}`,
58581
- valueType: leaf.valueType,
58582
- ...leaf.enumValues !== void 0 ? { enumValues: leaf.enumValues } : {}
58583
- });
58794
+ for (const device of ordered) {
58795
+ if (!isOsdLeafDeviceType(device.type ?? null)) continue;
58796
+ for (const capName of capsByDevice.get(device.id) ?? []) {
58797
+ if (!isOsdSourceCap(capName)) continue;
58798
+ const cap = capsByName.get(capName);
58799
+ if (cap === void 0) continue;
58800
+ for (const leaf of osdStateLeavesOf(cap)) options.push({
58801
+ deviceId: device.id,
58802
+ deviceName: device.name,
58803
+ capName,
58804
+ valuePath: leaf.path,
58805
+ label: `${humanizeCapName(capName)} · ${leaf.path}`,
58806
+ valueType: leaf.valueType,
58807
+ ...leaf.enumValues !== void 0 ? { enumValues: leaf.enumValues } : {}
58808
+ });
58809
+ }
58584
58810
  }
58585
58811
  return options;
58586
58812
  }
@@ -58976,7 +59202,7 @@ var OsdManager = class {
58976
59202
  if (stored === void 0) throw new Error(`osd-manager: template "${template.id}" missing after upsert`);
58977
59203
  this.logger.info("osd-manager: named template upserted", { meta: {
58978
59204
  templateId: stored.id,
58979
- fieldCount: stored.fields.length
59205
+ bindingCount: stored.bindings.length
58980
59206
  } });
58981
59207
  return { template: stored };
58982
59208
  };
@@ -59064,7 +59290,7 @@ var OsdManager = class {
59064
59290
  const templatesById = /* @__PURE__ */ new Map();
59065
59291
  for (const named of allTemplates) {
59066
59292
  templatesById.set(named.id, named);
59067
- if (templateIds.has(named.id)) for (const field of named.fields) wanted.add(field.deviceId);
59293
+ if (templateIds.has(named.id)) for (const binding of named.bindings) wanted.add(binding.pick.deviceId);
59068
59294
  }
59069
59295
  const stateByDevice = /* @__PURE__ */ new Map();
59070
59296
  const gateState = /* @__PURE__ */ new Map();
@@ -59266,7 +59492,8 @@ var OsdManagerAddon = class extends BaseAddon {
59266
59492
  listDevices: async () => {
59267
59493
  return (await ctx.api.deviceManager.listAll.query({ projection: "slim" })).map((d) => ({
59268
59494
  id: d.id,
59269
- name: d.name
59495
+ name: d.name,
59496
+ type: d.type ?? null
59270
59497
  }));
59271
59498
  },
59272
59499
  listCameras: async () => {
@@ -59364,4 +59591,41 @@ var OsdManagerAddon = class extends BaseAddon {
59364
59591
  }
59365
59592
  };
59366
59593
  //#endregion
59367
- export { DEFAULT_TICK_MS, EMPTY_BINDINGS, OSD_BINDINGS_KEY, OSD_SUPPORTED_CONDITIONS, OsdBindingsStore, OsdDeviceBindingsSchema, OsdManager, OsdManagerAddon, OsdManagerAddon as default, applyTemplate, assertSupportedConditions, displayValue, evaluateGate, formatClock, formatSlotText, humanizeCapName, limitText, readStatePath, renderSlot, resolveSource, sourceOptionsFor, stateLeavesOf, truncateDecimals, unsupportedConditionKeys, withBinding, withoutBinding };
59594
+ //#region src/osd-picker-devices.ts
59595
+ /**
59596
+ * Devices offered to the OSD DevicePicker — containers INCLUDED so the
59597
+ * operator can pick a gateway, then an OSD-supported child.
59598
+ */
59599
+ function toOsdPickerDevices(devices) {
59600
+ return devices.map((d) => ({
59601
+ id: d.id,
59602
+ name: d.name,
59603
+ type: d.type ?? null,
59604
+ location: d.location ?? null,
59605
+ parentDeviceId: d.parentDeviceId ?? null
59606
+ }));
59607
+ }
59608
+ /** Children of a container that have at least one OSD catalog field. */
59609
+ function osdSupportedChildrenOf(containerId, devices, deviceIdsWithOsdFields) {
59610
+ return devices.filter((d) => d.parentDeviceId === containerId && isOsdLeafDeviceType(d.type) && deviceIdsWithOsdFields.has(d.id)).toSorted((a, b) => a.name.localeCompare(b.name));
59611
+ }
59612
+ function deviceById(devices, id) {
59613
+ return devices.find((d) => d.id === id);
59614
+ }
59615
+ //#endregion
59616
+ //#region src/template-id.ts
59617
+ /**
59618
+ * Named-template ids are system-generated — never an operator text field.
59619
+ * Create uses a uuid; the editable surface is the template name.
59620
+ */
59621
+ function generateTemplateId() {
59622
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
59623
+ return `tpl-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
59624
+ }
59625
+ /** Optional slug helper when a stable short id from the name is preferred. */
59626
+ function templateIdFromName(name) {
59627
+ const slug = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
59628
+ return `${slug.length > 0 ? slug : "template"}-${Date.now().toString(36)}`;
59629
+ }
59630
+ //#endregion
59631
+ export { DEFAULT_TICK_MS, EMPTY_BINDINGS, OSD_BINDINGS_KEY, OSD_NON_LEAF_TYPES, OSD_PICKER_EXCLUDED_TYPES, OSD_SOURCE_CAP_NAMES, OSD_SUPPORTED_CONDITIONS, OsdBindingsStore, OsdDeviceBindingsSchema, OsdManager, OsdManagerAddon, OsdManagerAddon as default, aliasFullToken, aliasToken, applyTemplate, applyTemplateFormat, applyTemplateTokenKind, assertSupportedConditions, bindingInsertChips, bindingToken, convertForDisplay, deviceById, dimensionFromSourceUnit, displayValue, evaluateGate, expandBindingTokens, formatClock, formatSlotText, generateTemplateId, humanizeCapName, inferSourceUnit, isContainerDeviceType, isOsdLeafDeviceType, isOsdPickerDeviceType, isOsdSourceCap, limitText, osdStateLeavesOf, osdSupportedChildrenOf, parseAliasStems, parseAliasTokens, readStatePath, renderNamedTemplate, renderSlot, replaceToken, resolveBindingLive, resolveSource, sourceOptionsFor, stateLeavesOf, suggestAlias, templateIdFromName, toOsdPickerDevices, truncateDecimals, unsupportedConditionKeys, withBinding, withoutBinding };
@@ -1,5 +1,5 @@
1
1
  import { b as e, g as t, l as n, u as r } from "./_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare__react__loadShare__.js-C5jUMd7X.mjs";
2
- import { c as i, s as a } from "./responsive-B0yDP9vf.mjs";
2
+ import { c as i, s as a } from "./responsive-DLFs-5i_.mjs";
3
3
  import { n as o, r as s, t as c } from "./_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-DalfdIDw.mjs";
4
4
  var l = i("chevron-down", [["path", {
5
5
  d: "m6 9 6 6 6-6",
@@ -1,2 +1,2 @@
1
- import { n as e, t } from "./virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_osd_manager_page__remoteEntry_js-UY9P_4V2.mjs";
1
+ import { n as e, t } from "./virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_osd_manager_page__remoteEntry_js-C3yC0JaQ.mjs";
2
2
  export { t as get, e as init };
@@ -1,6 +1,6 @@
1
1
  import { _ as e, b as t, c as n, g as r, l as i, m as a, n as o, r as s, t as c, u as l } from "./_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare__react__loadShare__.js-C5jUMd7X.mjs";
2
2
  import "./_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-DalfdIDw.mjs";
3
- import { i as u } from "./_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BtgV-Ym5.mjs";
3
+ import { i as u } from "./_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-B2mEdhqd.mjs";
4
4
  //#region ../../node_modules/lucide-react/dist/esm/shared/src/utils/mergeClasses.js
5
5
  t();
6
6
  var d = (...e) => e.filter((e, t, n) => !!e && e.trim() !== "" && n.indexOf(e) === t).join(" ").trim(), f = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(), p = (e) => e.replace(/^([A-Z])|[\s-_]+(\w)/g, (e, t, n) => n ? n.toUpperCase() : t.toLowerCase()), m = (e) => {
@@ -1,5 +1,5 @@
1
1
  import { b as e, c as t, g as n, m as r } from "./_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare__react__loadShare__.js-C5jUMd7X.mjs";
2
- import { c as i } from "./responsive-B0yDP9vf.mjs";
2
+ import { c as i } from "./responsive-DLFs-5i_.mjs";
3
3
  var a = i("camera", [["path", {
4
4
  d: "M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z",
5
5
  key: "18u6gg"
@@ -1,4 +1,4 @@
1
- import { c as e } from "./responsive-B0yDP9vf.mjs";
1
+ import { c as e } from "./responsive-DLFs-5i_.mjs";
2
2
  var t = e("eye-off", [
3
3
  ["path", {
4
4
  d: "M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49",
@@ -1,4 +1,4 @@
1
- import { c as e } from "./responsive-B0yDP9vf.mjs";
1
+ import { c as e } from "./responsive-DLFs-5i_.mjs";
2
2
  var t = e("trash-2", [
3
3
  ["path", {
4
4
  d: "M10 11v6",
@@ -2753,7 +2753,7 @@ async function rr(e) {
2753
2753
  }
2754
2754
  }
2755
2755
  async function ir() {
2756
- return tr ||= rr(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_osd_manager_page-DdAtpC7z.mjs")).catch((e) => {
2756
+ return tr ||= rr(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_osd_manager_page-BYQUZdGL.mjs")).catch((e) => {
2757
2757
  throw tr = void 0, e;
2758
2758
  }), tr;
2759
2759
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-osd-manager",
3
- "version": "0.1.157",
3
+ "version": "0.1.161",
4
4
  "description": "Binds camera on-screen-display slots to live state and recognitions",
5
5
  "keywords": [
6
6
  "camstack",