@camstack/types 1.2.447 → 1.2.449

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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_sleep = require("./sleep-UXDHn-oJ.js");
2
+ const require_sleep = require("./sleep-BSPhInRm.js");
3
3
  const require_event_category = require("./event-category-E17zaHjN.js");
4
4
  const require_composition = require("./composition-CO6Sak-s.js");
5
5
  const require_node_color = require("./node-color.js");
@@ -66607,6 +66607,39 @@ var EMPTY_REMOTE_ASSUMED_STATE = {
66607
66607
  muted: null
66608
66608
  };
66609
66609
  var MAX_LAYOUT_SPAN = 24;
66610
+ var MAX_LAYOUT_GROUPS = 50;
66611
+ /**
66612
+ * Closed vocabularies the UI knows how to DRAW (D973). A layout names a shape,
66613
+ * a tone or a frame; it never carries a style. An addon that contributes a
66614
+ * component picks from these lists — it ships no UI code.
66615
+ */
66616
+ var REMOTE_BUTTON_SHAPES = [
66617
+ "rect",
66618
+ "rounded",
66619
+ "round",
66620
+ "pill"
66621
+ ];
66622
+ var RemoteButtonShapeSchema = zod.z.enum(REMOTE_BUTTON_SHAPES);
66623
+ var REMOTE_BUTTON_TONES = [
66624
+ "neutral",
66625
+ "accent",
66626
+ "danger",
66627
+ "red",
66628
+ "green",
66629
+ "yellow",
66630
+ "blue"
66631
+ ];
66632
+ var RemoteButtonToneSchema = zod.z.enum(REMOTE_BUTTON_TONES);
66633
+ var REMOTE_GROUP_FRAMES = [
66634
+ "none",
66635
+ "panel",
66636
+ "divider",
66637
+ "dpad",
66638
+ "rocker-v",
66639
+ "rocker-h",
66640
+ "keypad"
66641
+ ];
66642
+ var RemoteGroupFrameSchema = zod.z.enum(REMOTE_GROUP_FRAMES);
66610
66643
  var RemoteLayoutButtonSchema = zod.z.object({
66611
66644
  id: zod.z.string().min(1).max(40),
66612
66645
  /** 0-based grid position. */
@@ -66616,12 +66649,94 @@ var RemoteLayoutButtonSchema = zod.z.object({
66616
66649
  colSpan: zod.z.number().int().min(1).max(24),
66617
66650
  icon: zod.z.string().min(1).max(60).optional(),
66618
66651
  label: zod.z.string().min(1).max(40).optional(),
66619
- role: RemoteRoleSchema
66652
+ role: RemoteRoleSchema,
66653
+ /** Absent = the plain rendering every layout had before shapes existed. */
66654
+ shape: RemoteButtonShapeSchema.optional(),
66655
+ tone: RemoteButtonToneSchema.optional(),
66656
+ /** The group this button belongs to; it must lie inside that group's rectangle. */
66657
+ groupId: zod.z.string().min(1).max(40).optional()
66658
+ });
66659
+ /**
66660
+ * A rectangle of the grid that moves as ONE thing and is drawn with a frame: a
66661
+ * titled panel, a section divider, a D-pad ring. `componentId` names the palette
66662
+ * entry it came from (informational: the layout is self-contained).
66663
+ */
66664
+ var RemoteLayoutGroupSchema = zod.z.object({
66665
+ id: zod.z.string().min(1).max(40),
66666
+ componentId: zod.z.string().min(1).max(80),
66667
+ frame: RemoteGroupFrameSchema,
66668
+ row: zod.z.number().int().min(0).max(24),
66669
+ col: zod.z.number().int().min(0).max(24),
66670
+ rowSpan: zod.z.number().int().min(1).max(24),
66671
+ colSpan: zod.z.number().int().min(1).max(24),
66672
+ title: zod.z.string().min(1).max(40).optional()
66620
66673
  });
66674
+ function rectsOverlap(a, b) {
66675
+ return a.row < b.row + b.rowSpan && b.row < a.row + a.rowSpan && a.col < b.col + b.colSpan && b.col < a.col + a.colSpan;
66676
+ }
66677
+ function rectContains(outer, inner) {
66678
+ return inner.row >= outer.row && inner.col >= outer.col && inner.row + inner.rowSpan <= outer.row + outer.rowSpan && inner.col + inner.colSpan <= outer.col + outer.colSpan;
66679
+ }
66680
+ /**
66681
+ * The group rules of a layout: inside the grid, no two groups overlap, a
66682
+ * member lies inside its group, a divider holds nothing, and a button that is
66683
+ * not a member never sits inside someone else's rectangle (a group moves as
66684
+ * one thing, so a stray key under it would be left behind).
66685
+ */
66686
+ function refineGroups(grid, ctx) {
66687
+ const groups = grid.groups ?? [];
66688
+ const byId = /* @__PURE__ */ new Map();
66689
+ const fail = (message, path) => {
66690
+ ctx.addIssue({
66691
+ code: "custom",
66692
+ message,
66693
+ path
66694
+ });
66695
+ };
66696
+ groups.forEach((group, index) => {
66697
+ if (byId.has(group.id)) fail(`group id "${group.id}" is used twice`, [
66698
+ "groups",
66699
+ index,
66700
+ "id"
66701
+ ]);
66702
+ byId.set(group.id, group);
66703
+ if (group.row + group.rowSpan > grid.rows || group.col + group.colSpan > grid.columns) fail(`group "${group.id}" leaves the grid`, ["groups", index]);
66704
+ for (let other = 0; other < index; other += 1) {
66705
+ const earlier = groups[other];
66706
+ if (earlier !== void 0 && rectsOverlap(earlier, group)) {
66707
+ fail(`groups "${earlier.id}" and "${group.id}" overlap`, ["groups", index]);
66708
+ return;
66709
+ }
66710
+ }
66711
+ });
66712
+ grid.buttons.forEach((button, index) => {
66713
+ if (button.groupId !== void 0) {
66714
+ const group = byId.get(button.groupId);
66715
+ if (group === void 0) {
66716
+ fail(`button "${button.id}" names the unknown group "${button.groupId}"`, [
66717
+ "buttons",
66718
+ index,
66719
+ "groupId"
66720
+ ]);
66721
+ return;
66722
+ }
66723
+ if (group.frame === "divider") fail(`a divider group ("${group.id}") holds no buttons`, [
66724
+ "buttons",
66725
+ index,
66726
+ "groupId"
66727
+ ]);
66728
+ if (!rectContains(group, button)) fail(`button "${button.id}" lies outside its group "${group.id}"`, ["buttons", index]);
66729
+ return;
66730
+ }
66731
+ const foreign = groups.find((group) => rectsOverlap(group, button));
66732
+ if (foreign !== void 0) fail(`button "${button.id}" lies inside group "${foreign.id}" but is not a member`, ["buttons", index]);
66733
+ });
66734
+ }
66621
66735
  var RemoteLayoutPayloadSchema = zod.z.object({
66622
66736
  columns: zod.z.number().int().min(1).max(24),
66623
66737
  rows: zod.z.number().int().min(1).max(24),
66624
- buttons: zod.z.array(RemoteLayoutButtonSchema).max(200)
66738
+ buttons: zod.z.array(RemoteLayoutButtonSchema).max(200),
66739
+ groups: zod.z.array(RemoteLayoutGroupSchema).max(50).optional()
66625
66740
  }).superRefine((grid, ctx) => {
66626
66741
  const ids = /* @__PURE__ */ new Set();
66627
66742
  const taken = /* @__PURE__ */ new Map();
@@ -66658,6 +66773,7 @@ var RemoteLayoutPayloadSchema = zod.z.object({
66658
66773
  taken.set(cell, button.id);
66659
66774
  }
66660
66775
  });
66776
+ refineGroups(grid, ctx);
66661
66777
  });
66662
66778
  //#endregion
66663
66779
  //#region src/capabilities/remote.cap.ts
@@ -66887,6 +67003,121 @@ var templateCapability = {
66887
67003
  }
66888
67004
  };
66889
67005
  //#endregion
67006
+ //#region src/remote/remote-components.ts
67007
+ /**
67008
+ * A remote layout COMPONENT (D973): a reusable, named chunk of a remote — a
67009
+ * D-pad, a keypad, a section divider — offered in the template editor's
67010
+ * "Add component" palette. Pure data: the buttons are placed RELATIVE to the
67011
+ * component, and the frame, shape and tone are names from the closed
67012
+ * vocabularies the UI draws. An addon contributes components through the
67013
+ * `remote-layout-components` cap and ships no UI code.
67014
+ */
67015
+ var REMOTE_COMPONENT_CATEGORIES = [
67016
+ "power",
67017
+ "navigation",
67018
+ "volume",
67019
+ "numbers",
67020
+ "media",
67021
+ "colour",
67022
+ "apps",
67023
+ "layout"
67024
+ ];
67025
+ var RemoteComponentCategorySchema = zod.z.enum(REMOTE_COMPONENT_CATEGORIES);
67026
+ var MAX_COMPONENT_BUTTONS = 40;
67027
+ /** `<namespace>:<name>`, both lowercase slugs — `core:dpad`, `<addonId>:<name>`. */
67028
+ var COMPONENT_ID_RE = /^[a-z0-9][a-z0-9_.-]*:[a-z0-9][a-z0-9_-]*$/;
67029
+ var RemoteComponentButtonSchema = zod.z.object({
67030
+ /** Joined to the instance id when the component is inserted. */
67031
+ idSuffix: zod.z.string().regex(/^[a-z0-9][a-z0-9_-]{0,19}$/, { message: "a short lowercase slug" }),
67032
+ row: zod.z.number().int().min(0).max(24),
67033
+ col: zod.z.number().int().min(0).max(24),
67034
+ rowSpan: zod.z.number().int().min(1).max(24),
67035
+ colSpan: zod.z.number().int().min(1).max(24),
67036
+ role: RemoteRoleSchema,
67037
+ icon: zod.z.string().min(1).max(60).optional(),
67038
+ label: zod.z.string().min(1).max(40).optional(),
67039
+ shape: RemoteButtonShapeSchema.optional(),
67040
+ tone: RemoteButtonToneSchema.optional()
67041
+ });
67042
+ var RemoteLayoutComponentSchema = zod.z.object({
67043
+ id: zod.z.string().max(80).regex(COMPONENT_ID_RE, { message: "<namespace>:<name>, lowercase" }),
67044
+ name: zod.z.string().min(1).max(60),
67045
+ nameI18n: zod.z.object({
67046
+ en: zod.z.string().min(1).max(60),
67047
+ it: zod.z.string().min(1).max(60)
67048
+ }).optional(),
67049
+ /** A display icon key, resolved by the UI like a button icon. */
67050
+ icon: zod.z.string().min(1).max(60),
67051
+ category: RemoteComponentCategorySchema,
67052
+ size: zod.z.object({
67053
+ rows: zod.z.number().int().min(1).max(24),
67054
+ cols: zod.z.number().int().min(1).max(24)
67055
+ }),
67056
+ /** The frame drawn around the whole component, or null for loose buttons. */
67057
+ group: zod.z.object({
67058
+ frame: RemoteGroupFrameSchema,
67059
+ title: zod.z.string().min(1).max(40).optional()
67060
+ }).nullable(),
67061
+ buttons: zod.z.array(RemoteComponentButtonSchema).max(40)
67062
+ }).superRefine((component, ctx) => {
67063
+ const suffixes = /* @__PURE__ */ new Set();
67064
+ component.buttons.forEach((button, index) => {
67065
+ if (suffixes.has(button.idSuffix)) ctx.addIssue({
67066
+ code: "custom",
67067
+ message: `button "${button.idSuffix}" is listed twice`,
67068
+ path: [
67069
+ "buttons",
67070
+ index,
67071
+ "idSuffix"
67072
+ ]
67073
+ });
67074
+ suffixes.add(button.idSuffix);
67075
+ if (button.row + button.rowSpan > component.size.rows || button.col + button.colSpan > component.size.cols) {
67076
+ ctx.addIssue({
67077
+ code: "custom",
67078
+ message: `button "${button.idSuffix}" leaves the component`,
67079
+ path: ["buttons", index]
67080
+ });
67081
+ return;
67082
+ }
67083
+ for (let other = 0; other < index; other += 1) {
67084
+ const earlier = component.buttons[other];
67085
+ if (earlier !== void 0 && rectsOverlap(earlier, button)) {
67086
+ ctx.addIssue({
67087
+ code: "custom",
67088
+ message: `buttons "${earlier.idSuffix}" and "${button.idSuffix}" overlap`,
67089
+ path: ["buttons", index]
67090
+ });
67091
+ return;
67092
+ }
67093
+ }
67094
+ });
67095
+ if (component.group?.frame === "divider" && component.buttons.length > 0) ctx.addIssue({
67096
+ code: "custom",
67097
+ message: "a divider component holds no buttons",
67098
+ path: ["buttons"]
67099
+ });
67100
+ });
67101
+ //#endregion
67102
+ //#region src/capabilities/remote-layout-components.cap.ts
67103
+ /**
67104
+ * remote-layout-components — the palette of the remote template editor's "Add
67105
+ * component" (D973). A provider DECLARES components as data (buttons placed
67106
+ * relative to the component, a frame, shapes and tones from closed
67107
+ * vocabularies the UI draws); it ships no UI code.
67108
+ *
67109
+ * Collection: `listComponents` returns an array, so the generated mount
67110
+ * concat-fans it across every enabled provider. The core-blocks builtin
67111
+ * provides the built-in set (`core:*`); an addon adds its own under
67112
+ * `<addonId>:<name>`. The caller dedupes by id, built-ins first.
67113
+ */
67114
+ var remoteLayoutComponentsCapability = {
67115
+ name: "remote-layout-components",
67116
+ scope: "system",
67117
+ mode: "collection",
67118
+ methods: { listComponents: require_sleep.method(zod.z.object({}), zod.z.array(RemoteLayoutComponentSchema), { auth: "protected" }) }
67119
+ };
67120
+ //#endregion
66890
67121
  //#region src/capabilities/button.cap.ts
66891
67122
  /**
66892
67123
  * button — device-scoped capability for HA `button.*` / `input_button.*`
@@ -82956,6 +83187,467 @@ var PIPELINE_OWNER_CAPABILITY_NAMES = [
82956
83187
  "pipeline-runner"
82957
83188
  ];
82958
83189
  //#endregion
83190
+ //#region src/remote/remote-layout-ops.ts
83191
+ var LAYOUT_EDIT_REFUSALS = [
83192
+ "outside",
83193
+ "overlap",
83194
+ "unknown-group",
83195
+ "too-many"
83196
+ ];
83197
+ var refuse = (reason) => ({
83198
+ ok: false,
83199
+ reason
83200
+ });
83201
+ function withGroups(layout, groups) {
83202
+ const { groups: _previous, ...rest } = layout;
83203
+ return groups.length === 0 ? rest : {
83204
+ ...rest,
83205
+ groups: [...groups]
83206
+ };
83207
+ }
83208
+ function fitsGrid(layout, rect) {
83209
+ return rect.row >= 0 && rect.col >= 0 && rect.row + rect.rowSpan <= layout.rows && rect.col + rect.colSpan <= layout.columns;
83210
+ }
83211
+ /** The columns a component must be scaled by to span the whole grid width, or null when it does not divide. */
83212
+ function fillWidthScale(layout, component) {
83213
+ const factor = layout.columns / component.size.cols;
83214
+ return Number.isInteger(factor) && factor >= 1 ? factor : null;
83215
+ }
83216
+ function titleOf(title) {
83217
+ return title === void 0 ? {} : { title };
83218
+ }
83219
+ function slug$1(value) {
83220
+ return (value.includes(":") ? value.slice(value.indexOf(":") + 1) : value).replace(/[^a-z0-9_-]/g, "-").slice(0, 16);
83221
+ }
83222
+ function uniqueId(base, taken) {
83223
+ if (!taken.has(base)) return base;
83224
+ for (let n = 2;; n += 1) {
83225
+ const candidate = `${base}-${n}`;
83226
+ if (!taken.has(candidate)) return candidate;
83227
+ }
83228
+ }
83229
+ function freePrefix(base, placed, taken) {
83230
+ for (let n = 1;; n += 1) {
83231
+ const candidate = n === 1 ? base : `${base}-${n}`;
83232
+ if (!placed.some((p) => taken.has(`${candidate}-${p.spec.idSuffix}`))) return candidate;
83233
+ }
83234
+ }
83235
+ /**
83236
+ * Put `component` on the layout with its top-left cell at `at`. A component
83237
+ * with a group becomes one group whose rectangle is the whole component and
83238
+ * whose members are its buttons; one without is a set of loose buttons. The
83239
+ * grid does not grow: a component that does not fit is refused `outside`, one
83240
+ * that would sit on an existing key or group `overlap`.
83241
+ */
83242
+ function insertComponent(layout, component, at, options = {}) {
83243
+ const rowScale = options.scale?.rows ?? 1;
83244
+ const colScale = options.scale?.cols ?? 1;
83245
+ const bounds = {
83246
+ row: at.row,
83247
+ col: at.col,
83248
+ rowSpan: component.size.rows * rowScale,
83249
+ colSpan: component.size.cols * colScale
83250
+ };
83251
+ if (!fitsGrid(layout, bounds)) return refuse("outside");
83252
+ const specs = [...component.buttons, ...options.extras ?? []];
83253
+ const placed = specs.map((spec) => ({
83254
+ spec,
83255
+ rect: {
83256
+ row: at.row + spec.row * rowScale,
83257
+ col: at.col + spec.col * colScale,
83258
+ rowSpan: spec.rowSpan * rowScale,
83259
+ colSpan: spec.colSpan * colScale
83260
+ }
83261
+ }));
83262
+ const groups = layout.groups ?? [];
83263
+ if ((component.group === null ? placed.map((p) => p.rect) : [bounds]).some((rect) => layout.buttons.some((b) => rectsOverlap(b, rect)) || groups.some((g) => rectsOverlap(g, rect)))) return refuse("overlap");
83264
+ if (layout.buttons.length + specs.length > 200 || groups.length + (component.group === null ? 0 : 1) > 50) return refuse("too-many");
83265
+ const base = slug$1(component.id);
83266
+ const takenButtons = new Set(layout.buttons.map((b) => b.id));
83267
+ const takenGroups = new Set(groups.map((g) => g.id));
83268
+ const groupId = component.group === null ? null : uniqueId(base, takenGroups);
83269
+ const prefix = groupId ?? freePrefix(base, placed, takenButtons);
83270
+ const buttons = placed.map(({ spec, rect }) => ({
83271
+ id: `${prefix}-${spec.idSuffix}`,
83272
+ ...rect,
83273
+ role: spec.role,
83274
+ ...spec.icon === void 0 ? {} : { icon: spec.icon },
83275
+ ...spec.label === void 0 ? {} : { label: spec.label },
83276
+ ...spec.shape === void 0 ? {} : { shape: spec.shape },
83277
+ ...spec.tone === void 0 ? {} : { tone: spec.tone },
83278
+ ...groupId === null ? {} : { groupId }
83279
+ }));
83280
+ const group = component.group === null || groupId === null ? [] : [{
83281
+ id: groupId,
83282
+ componentId: component.id,
83283
+ frame: component.group.frame,
83284
+ ...bounds,
83285
+ ...titleOf(options.title ?? component.group.title)
83286
+ }];
83287
+ return {
83288
+ ok: true,
83289
+ layout: withGroups({
83290
+ ...layout,
83291
+ buttons: [...layout.buttons, ...buttons]
83292
+ }, [...groups, ...group]),
83293
+ groupId,
83294
+ buttonIds: buttons.map((b) => b.id)
83295
+ };
83296
+ }
83297
+ /** Move a group and every member by the same offset, atomically. */
83298
+ function moveGroup(layout, groupId, to) {
83299
+ const groups = layout.groups ?? [];
83300
+ const group = groups.find((g) => g.id === groupId);
83301
+ if (group === void 0) return refuse("unknown-group");
83302
+ const moved = {
83303
+ ...group,
83304
+ row: to.row,
83305
+ col: to.col
83306
+ };
83307
+ if (!fitsGrid(layout, moved)) return refuse("outside");
83308
+ const dRow = to.row - group.row;
83309
+ const dCol = to.col - group.col;
83310
+ const isMember = (b) => b.groupId === groupId;
83311
+ if (groups.some((g) => g.id !== groupId && rectsOverlap(g, moved)) || layout.buttons.some((b) => !isMember(b) && rectsOverlap(b, moved))) return refuse("overlap");
83312
+ return {
83313
+ ok: true,
83314
+ layout: {
83315
+ ...layout,
83316
+ buttons: layout.buttons.map((b) => isMember(b) ? {
83317
+ ...b,
83318
+ row: b.row + dRow,
83319
+ col: b.col + dCol
83320
+ } : b),
83321
+ groups: groups.map((g) => g.id === groupId ? moved : g)
83322
+ }
83323
+ };
83324
+ }
83325
+ /**
83326
+ * Delete a group. With `keepButtons` its members stay where they are as loose
83327
+ * keys; without, they go with it.
83328
+ */
83329
+ function removeGroup(layout, groupId, options) {
83330
+ const groups = layout.groups ?? [];
83331
+ if (!groups.some((g) => g.id === groupId)) return refuse("unknown-group");
83332
+ const buttons = options.keepButtons ? layout.buttons.map((b) => {
83333
+ if (b.groupId !== groupId) return b;
83334
+ const { groupId: _gone, ...rest } = b;
83335
+ return rest;
83336
+ }) : layout.buttons.filter((b) => b.groupId !== groupId);
83337
+ return {
83338
+ ok: true,
83339
+ layout: withGroups({
83340
+ ...layout,
83341
+ buttons
83342
+ }, groups.filter((g) => g.id !== groupId))
83343
+ };
83344
+ }
83345
+ /** Dissolve a group, keeping its buttons in place. */
83346
+ function ungroup(layout, groupId) {
83347
+ return removeGroup(layout, groupId, { keepButtons: true });
83348
+ }
83349
+ //#endregion
83350
+ //#region src/remote/remote-builtin-components.ts
83351
+ var btn = (idSuffix, row, col, init) => ({
83352
+ idSuffix,
83353
+ row,
83354
+ col,
83355
+ rowSpan: 1,
83356
+ colSpan: 1,
83357
+ ...init
83358
+ });
83359
+ function rocker(id, name, it, frame, title, buttons, size) {
83360
+ return {
83361
+ id,
83362
+ name,
83363
+ nameI18n: {
83364
+ en: name,
83365
+ it
83366
+ },
83367
+ icon: frame === "rocker-v" ? "chevrons-up-down" : "chevrons-left-right",
83368
+ category: id.includes("channel") || id.includes("volume") ? "volume" : "navigation",
83369
+ size,
83370
+ group: title === null ? { frame } : {
83371
+ frame,
83372
+ title
83373
+ },
83374
+ buttons
83375
+ };
83376
+ }
83377
+ var BUILTIN_REMOTE_COMPONENTS = [
83378
+ {
83379
+ id: "core:power",
83380
+ name: "Power",
83381
+ nameI18n: {
83382
+ en: "Power",
83383
+ it: "Accensione"
83384
+ },
83385
+ icon: "power",
83386
+ category: "power",
83387
+ size: {
83388
+ rows: 1,
83389
+ cols: 1
83390
+ },
83391
+ group: null,
83392
+ buttons: [btn("power", 0, 0, {
83393
+ role: "power",
83394
+ shape: "round",
83395
+ tone: "danger"
83396
+ })]
83397
+ },
83398
+ {
83399
+ id: "core:dpad",
83400
+ name: "D-pad with OK",
83401
+ nameI18n: {
83402
+ en: "D-pad with OK",
83403
+ it: "Croce direzionale con OK"
83404
+ },
83405
+ icon: "gamepad-2",
83406
+ category: "navigation",
83407
+ size: {
83408
+ rows: 3,
83409
+ cols: 3
83410
+ },
83411
+ group: { frame: "dpad" },
83412
+ buttons: [
83413
+ btn("up", 0, 1, {
83414
+ role: "up",
83415
+ icon: "chevron-up"
83416
+ }),
83417
+ btn("left", 1, 0, {
83418
+ role: "left",
83419
+ icon: "chevron-left"
83420
+ }),
83421
+ btn("ok", 1, 1, {
83422
+ role: "ok",
83423
+ shape: "round",
83424
+ tone: "accent"
83425
+ }),
83426
+ btn("right", 1, 2, {
83427
+ role: "right",
83428
+ icon: "chevron-right"
83429
+ }),
83430
+ btn("down", 2, 1, {
83431
+ role: "down",
83432
+ icon: "chevron-down"
83433
+ })
83434
+ ]
83435
+ },
83436
+ rocker("core:rocker-volume-v", "Volume rocker", "Bilanciere volume", "rocker-v", "VOL", [btn("up", 0, 0, {
83437
+ role: "volume_up",
83438
+ icon: "plus",
83439
+ shape: "pill"
83440
+ }), btn("down", 1, 0, {
83441
+ role: "volume_down",
83442
+ icon: "minus",
83443
+ shape: "pill"
83444
+ })], {
83445
+ rows: 2,
83446
+ cols: 1
83447
+ }),
83448
+ rocker("core:rocker-channel-v", "Channel rocker", "Bilanciere canali", "rocker-v", "CH", [btn("up", 0, 0, {
83449
+ role: "channel_up",
83450
+ icon: "chevron-up",
83451
+ shape: "pill"
83452
+ }), btn("down", 1, 0, {
83453
+ role: "channel_down",
83454
+ icon: "chevron-down",
83455
+ shape: "pill"
83456
+ })], {
83457
+ rows: 2,
83458
+ cols: 1
83459
+ }),
83460
+ rocker("core:rocker-h", "Minus / plus rocker", "Bilanciere meno / più", "rocker-h", null, [btn("minus", 0, 0, {
83461
+ role: "custom:minus",
83462
+ label: "Minus",
83463
+ icon: "minus",
83464
+ shape: "pill"
83465
+ }), btn("plus", 0, 1, {
83466
+ role: "custom:plus",
83467
+ label: "Plus",
83468
+ icon: "plus",
83469
+ shape: "pill"
83470
+ })], {
83471
+ rows: 1,
83472
+ cols: 2
83473
+ }),
83474
+ {
83475
+ id: "core:keypad",
83476
+ name: "Numeric keypad",
83477
+ nameI18n: {
83478
+ en: "Numeric keypad",
83479
+ it: "Tastierino numerico"
83480
+ },
83481
+ icon: "grid-3x3",
83482
+ category: "numbers",
83483
+ size: {
83484
+ rows: 4,
83485
+ cols: 3
83486
+ },
83487
+ group: { frame: "keypad" },
83488
+ buttons: [...Array.from({ length: 9 }, (_, i) => btn(`d${i + 1}`, Math.floor(i / 3), i % 3, { role: `digit_${i + 1}` })), btn("d0", 3, 1, { role: "digit_0" })]
83489
+ },
83490
+ {
83491
+ id: "core:colour-keys",
83492
+ name: "Colour keys",
83493
+ nameI18n: {
83494
+ en: "Colour keys",
83495
+ it: "Tasti colorati"
83496
+ },
83497
+ icon: "palette",
83498
+ category: "colour",
83499
+ size: {
83500
+ rows: 1,
83501
+ cols: 4
83502
+ },
83503
+ group: null,
83504
+ buttons: [
83505
+ btn("red", 0, 0, {
83506
+ role: "custom:color_red",
83507
+ label: "Red",
83508
+ tone: "red"
83509
+ }),
83510
+ btn("green", 0, 1, {
83511
+ role: "custom:color_green",
83512
+ label: "Green",
83513
+ tone: "green"
83514
+ }),
83515
+ btn("yellow", 0, 2, {
83516
+ role: "custom:color_yellow",
83517
+ label: "Yellow",
83518
+ tone: "yellow"
83519
+ }),
83520
+ btn("blue", 0, 3, {
83521
+ role: "custom:color_blue",
83522
+ label: "Blue",
83523
+ tone: "blue"
83524
+ })
83525
+ ]
83526
+ },
83527
+ {
83528
+ id: "core:media-transport",
83529
+ name: "Media transport",
83530
+ nameI18n: {
83531
+ en: "Media transport",
83532
+ it: "Controlli multimediali"
83533
+ },
83534
+ icon: "play",
83535
+ category: "media",
83536
+ size: {
83537
+ rows: 1,
83538
+ cols: 5
83539
+ },
83540
+ group: null,
83541
+ buttons: [
83542
+ btn("rewind", 0, 0, {
83543
+ role: "rewind",
83544
+ icon: "rewind"
83545
+ }),
83546
+ btn("play", 0, 1, {
83547
+ role: "play",
83548
+ icon: "play"
83549
+ }),
83550
+ btn("pause", 0, 2, {
83551
+ role: "pause",
83552
+ icon: "pause"
83553
+ }),
83554
+ btn("forward", 0, 3, {
83555
+ role: "fast_forward",
83556
+ icon: "fast-forward"
83557
+ }),
83558
+ btn("stop", 0, 4, {
83559
+ role: "stop",
83560
+ icon: "square"
83561
+ })
83562
+ ]
83563
+ },
83564
+ {
83565
+ id: "core:apps-row",
83566
+ name: "App row",
83567
+ nameI18n: {
83568
+ en: "App row",
83569
+ it: "Riga di app"
83570
+ },
83571
+ icon: "app-window",
83572
+ category: "apps",
83573
+ size: {
83574
+ rows: 1,
83575
+ cols: 4
83576
+ },
83577
+ group: null,
83578
+ buttons: [
83579
+ 1,
83580
+ 2,
83581
+ 3,
83582
+ 4
83583
+ ].map((n) => btn(`app${n}`, 0, n - 1, { role: `custom:app_slot_${n}` }))
83584
+ },
83585
+ {
83586
+ id: "core:back-home-menu",
83587
+ name: "Back, Home, Menu",
83588
+ nameI18n: {
83589
+ en: "Back, Home, Menu",
83590
+ it: "Indietro, Home, Menu"
83591
+ },
83592
+ icon: "house",
83593
+ category: "navigation",
83594
+ size: {
83595
+ rows: 1,
83596
+ cols: 3
83597
+ },
83598
+ group: null,
83599
+ buttons: [
83600
+ btn("back", 0, 0, { role: "back" }),
83601
+ btn("home", 0, 1, { role: "home" }),
83602
+ btn("menu", 0, 2, { role: "menu" })
83603
+ ]
83604
+ },
83605
+ {
83606
+ id: "core:section-divider",
83607
+ name: "Section divider",
83608
+ nameI18n: {
83609
+ en: "Section divider",
83610
+ it: "Divisore di sezione"
83611
+ },
83612
+ icon: "separator-horizontal",
83613
+ category: "layout",
83614
+ size: {
83615
+ rows: 1,
83616
+ cols: 1
83617
+ },
83618
+ group: {
83619
+ frame: "divider",
83620
+ title: "Section"
83621
+ },
83622
+ buttons: []
83623
+ },
83624
+ {
83625
+ id: "core:panel",
83626
+ name: "Titled panel",
83627
+ nameI18n: {
83628
+ en: "Titled panel",
83629
+ it: "Pannello con titolo"
83630
+ },
83631
+ icon: "panel-top",
83632
+ category: "layout",
83633
+ size: {
83634
+ rows: 2,
83635
+ cols: 3
83636
+ },
83637
+ group: {
83638
+ frame: "panel",
83639
+ title: "Panel"
83640
+ },
83641
+ buttons: []
83642
+ }
83643
+ ];
83644
+ /** A built-in component by id (the presets compose from these). */
83645
+ function builtinRemoteComponent(id) {
83646
+ const found = BUILTIN_REMOTE_COMPONENTS.find((component) => component.id === id);
83647
+ if (found === void 0) throw new Error(`no built-in remote component "${id}"`);
83648
+ return found;
83649
+ }
83650
+ //#endregion
82959
83651
  //#region src/ir/ir-error.ts
82960
83652
  var IrCodecError = class extends Error {
82961
83653
  code;
@@ -96959,6 +97651,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
96959
97651
  access: "create",
96960
97652
  role: "admin"
96961
97653
  },
97654
+ "remoteLayoutComponents.listComponents": {
97655
+ capName: "remote-layout-components",
97656
+ capScope: "system",
97657
+ addonId: null,
97658
+ access: "view",
97659
+ role: "admin"
97660
+ },
96962
97661
  "runningState.getStatus": {
96963
97662
  capName: "running-state",
96964
97663
  capScope: "device",
@@ -99322,6 +100021,7 @@ var KNOWN_CAP_NAMES = [
99322
100021
  "recording-signal",
99323
100022
  "records",
99324
100023
  "remote",
100024
+ "remote-layout-components",
99325
100025
  "running-state",
99326
100026
  "scene-monitor",
99327
100027
  "script-runner",
@@ -99542,6 +100242,7 @@ var SYSTEM_CAP_NAMES = [
99542
100242
  "recording-export",
99543
100243
  "records",
99544
100244
  "remote",
100245
+ "remote-layout-components",
99545
100246
  "server-management",
99546
100247
  "settings-store",
99547
100248
  "smtp-provider",
@@ -103540,6 +104241,7 @@ var SYSTEM_CAP_VOCABULARY = {
103540
104241
  "recording-export": system("recording exports — requested from the timeline, not an automation step"),
103541
104242
  records: system(INTERNAL),
103542
104243
  remote: system("the per-device remote setup — authored in the Customize remote editor, sent through the remote UI"),
104244
+ "remote-layout-components": system("the palette of the remote template editor — read-only data, never actuated"),
103543
104245
  "server-management": system(CLUSTER),
103544
104246
  "settings-store": system(INTERNAL),
103545
104247
  "smtp-provider": system(INTERNAL),
@@ -105527,6 +106229,7 @@ var CAPABILITY_NAMES = {
105527
106229
  recordingSignal: "recording-signal",
105528
106230
  records: "records",
105529
106231
  remote: "remote",
106232
+ remoteLayoutComponents: "remote-layout-components",
105530
106233
  runningState: "running-state",
105531
106234
  sceneMonitor: "scene-monitor",
105532
106235
  scriptRunner: "script-runner",
@@ -106166,6 +106869,10 @@ var CAPABILITY_ROUTER_KEYS = [
106166
106869
  key: "remote",
106167
106870
  name: "remote"
106168
106871
  },
106872
+ {
106873
+ key: "remoteLayoutComponents",
106874
+ name: "remote-layout-components"
106875
+ },
106169
106876
  {
106170
106877
  key: "runningState",
106171
106878
  name: "running-state"
@@ -106501,6 +107208,7 @@ var ALL_CAPABILITY_DEFINITIONS = [
106501
107208
  recordingSignalCapability,
106502
107209
  recordsCapability,
106503
107210
  remoteCapability,
107211
+ remoteLayoutComponentsCapability,
106504
107212
  runningStateCapability,
106505
107213
  sceneMonitorCapability,
106506
107214
  scriptRunnerCapability,
@@ -111291,6 +111999,7 @@ function createSystemProxy(api) {
111291
111999
  sendCommand: (input) => dispatch("remote", "sendCommand", "mutation", input),
111292
112000
  sendMediaCommand: (input) => dispatch("remote", "sendMediaCommand", "mutation", input)
111293
112001
  },
112002
+ remoteLayoutComponents: { listComponents: (input) => dispatch("remoteLayoutComponents", "listComponents", "query", input) },
111294
112003
  serverManagement: {
111295
112004
  getServerPackageStatus: (input) => dispatch("serverManagement", "getServerPackageStatus", "query", input),
111296
112005
  checkServerUpdate: (input) => dispatch("serverManagement", "checkServerUpdate", "mutation", input),
@@ -115526,8 +116235,6 @@ var NC_TEMPLATE_VARS = [
115526
116235
  * delete it from here then, and the served catalog names it again.
115527
116236
  */
115528
116237
  var NC_TEMPLATE_HELD_BACK_SYSTEM_EVENT_KINDS = [
115529
- "node-restarting",
115530
- "node-updating",
115531
116238
  "alert-critical",
115532
116239
  "alert-error",
115533
116240
  "alert-critical-resolved",
@@ -122371,6 +123078,7 @@ exports.BENCH_ROOT_STEP_ID = BENCH_ROOT_STEP_ID;
122371
123078
  exports.BINARY_AGGREGATES = require_composition.BINARY_AGGREGATES;
122372
123079
  exports.BIRTH_DECISION_DEFAULT_LIMIT = BIRTH_DECISION_DEFAULT_LIMIT;
122373
123080
  exports.BOOT_RECOVERY_BACKOFF_MS = require_sleep.BOOT_RECOVERY_BACKOFF_MS;
123081
+ exports.BUILTIN_REMOTE_COMPONENTS = BUILTIN_REMOTE_COMPONENTS;
122374
123082
  exports.BUILTIN_RESULT_KINDS = BUILTIN_RESULT_KINDS;
122375
123083
  exports.BackgroundRunRegistry = BackgroundRunRegistry;
122376
123084
  exports.BacklightModeSchema = BacklightModeSchema;
@@ -123099,6 +123807,7 @@ exports.KeychainWriteRefusedError = require_sleep.KeychainWriteRefusedError;
123099
123807
  exports.LABEL_SUPPLY_MACRO = LABEL_SUPPLY_MACRO;
123100
123808
  exports.LABEL_SUPPLY_STEP_IDS = LABEL_SUPPLY_STEP_IDS;
123101
123809
  exports.LABEL_SUPPLY_VOCABULARIES = LABEL_SUPPLY_VOCABULARIES;
123810
+ exports.LAYOUT_EDIT_REFUSALS = LAYOUT_EDIT_REFUSALS;
123102
123811
  exports.LEGACY_READ_ONLY_CONFIG_KEY = LEGACY_READ_ONLY_CONFIG_KEY;
123103
123812
  exports.LIGHT_SHARED_CAPS = LIGHT_SHARED_CAPS;
123104
123813
  exports.LINKED_DEVICES_SLOT = LINKED_DEVICES_SLOT;
@@ -123205,6 +123914,7 @@ exports.MAX_BIRTH_LATENCY_PROXY_MS = MAX_BIRTH_LATENCY_PROXY_MS;
123205
123914
  exports.MAX_CAMERA_SENSORS = MAX_CAMERA_SENSORS;
123206
123915
  exports.MAX_CLIP_EVENT_IDS = MAX_CLIP_EVENT_IDS;
123207
123916
  exports.MAX_CLIP_LABELS = MAX_CLIP_LABELS;
123917
+ exports.MAX_COMPONENT_BUTTONS = MAX_COMPONENT_BUTTONS;
123208
123918
  exports.MAX_COMPOSITION_CODE_LENGTH = require_composition.MAX_COMPOSITION_CODE_LENGTH;
123209
123919
  exports.MAX_COMPOSITION_FEATURES = require_composition.MAX_COMPOSITION_FEATURES;
123210
123920
  exports.MAX_COMPOSITION_FIELDS_PER_FEATURE = require_composition.MAX_COMPOSITION_FIELDS_PER_FEATURE;
@@ -123218,6 +123928,7 @@ exports.MAX_EXPRESSION_EVAL_STEPS = require_composition.MAX_EXPRESSION_EVAL_STEP
123218
123928
  exports.MAX_EXPRESSION_SOURCE_LENGTH = require_composition.MAX_EXPRESSION_SOURCE_LENGTH;
123219
123929
  exports.MAX_HELPER_MEMBERS = require_composition.MAX_HELPER_MEMBERS;
123220
123930
  exports.MAX_KEYS = MAX_KEYS;
123931
+ exports.MAX_LAYOUT_GROUPS = MAX_LAYOUT_GROUPS;
123221
123932
  exports.MAX_LAYOUT_SPAN = MAX_LAYOUT_SPAN;
123222
123933
  exports.MAX_REASONS_PER_KEY = MAX_REASONS_PER_KEY;
123223
123934
  exports.MAX_REMOTE_BINDINGS = MAX_REMOTE_BINDINGS;
@@ -123786,8 +124497,12 @@ exports.RECOVERY_VIEW_PX = RECOVERY_VIEW_PX;
123786
124497
  exports.REDACTED_SECRET = require_sleep.REDACTED_SECRET;
123787
124498
  exports.REFERENCE_COMPUTE = REFERENCE_COMPUTE;
123788
124499
  exports.REFERENCE_MATCH_FRACTION = REFERENCE_MATCH_FRACTION;
124500
+ exports.REMOTE_BUTTON_SHAPES = REMOTE_BUTTON_SHAPES;
124501
+ exports.REMOTE_BUTTON_TONES = REMOTE_BUTTON_TONES;
124502
+ exports.REMOTE_COMPONENT_CATEGORIES = REMOTE_COMPONENT_CATEGORIES;
123789
124503
  exports.REMOTE_CONFIG_REFUSALS = REMOTE_CONFIG_REFUSALS;
123790
124504
  exports.REMOTE_CUSTOM_ROLE_PREFIX = REMOTE_CUSTOM_ROLE_PREFIX;
124505
+ exports.REMOTE_GROUP_FRAMES = REMOTE_GROUP_FRAMES;
123791
124506
  exports.REMOTE_KNOWN_ROLES = REMOTE_KNOWN_ROLES;
123792
124507
  exports.REMOTE_MAX_REPEAT = REMOTE_MAX_REPEAT;
123793
124508
  exports.REMOTE_MEDIA_COMMANDS = REMOTE_MEDIA_COMMANDS;
@@ -123906,10 +124621,17 @@ exports.RemoteAssumedStateSchema = RemoteAssumedStateSchema;
123906
124621
  exports.RemoteBindingActionSchema = RemoteBindingActionSchema;
123907
124622
  exports.RemoteBindingSchema = RemoteBindingSchema;
123908
124623
  exports.RemoteBoundCommandSchema = RemoteBoundCommandSchema;
124624
+ exports.RemoteButtonShapeSchema = RemoteButtonShapeSchema;
124625
+ exports.RemoteButtonToneSchema = RemoteButtonToneSchema;
123909
124626
  exports.RemoteCommandListSchema = RemoteCommandListSchema;
124627
+ exports.RemoteComponentButtonSchema = RemoteComponentButtonSchema;
124628
+ exports.RemoteComponentCategorySchema = RemoteComponentCategorySchema;
123910
124629
  exports.RemoteConfigSchema = RemoteConfigSchema;
123911
124630
  exports.RemoteCustomButtonSchema = RemoteCustomButtonSchema;
124631
+ exports.RemoteGroupFrameSchema = RemoteGroupFrameSchema;
123912
124632
  exports.RemoteLayoutButtonSchema = RemoteLayoutButtonSchema;
124633
+ exports.RemoteLayoutComponentSchema = RemoteLayoutComponentSchema;
124634
+ exports.RemoteLayoutGroupSchema = RemoteLayoutGroupSchema;
123913
124635
  exports.RemoteLayoutPayloadSchema = RemoteLayoutPayloadSchema;
123914
124636
  exports.RemoteMediaCommandResultSchema = RemoteMediaCommandResultSchema;
123915
124637
  exports.RemoteMediaCommandSchema = RemoteMediaCommandSchema;
@@ -124481,6 +125203,7 @@ exports.buildTriggerTrace = buildTriggerTrace;
124481
125203
  exports.buildVideoArgs = require_composition.buildVideoArgs;
124482
125204
  exports.buildViewerLink = buildViewerLink;
124483
125205
  exports.builtCapabilityBlockers = builtCapabilityBlockers;
125206
+ exports.builtinRemoteComponent = builtinRemoteComponent;
124484
125207
  exports.bundledCamstack = bundledCamstack;
124485
125208
  exports.buttonCapability = buttonCapability;
124486
125209
  exports.calendarEventLocalSpan = calendarEventLocalSpan;
@@ -124770,6 +125493,7 @@ exports.fieldReads = require_composition.fieldReads;
124770
125493
  exports.fieldUnitKeys = fieldUnitKeys;
124771
125494
  exports.fieldsPinnedAtValueBelow = fieldsPinnedAtValueBelow;
124772
125495
  exports.filesystemBrowseCapability = filesystemBrowseCapability;
125496
+ exports.fillWidthScale = fillWidthScale;
124773
125497
  exports.findCandidateCycles = findCandidateCycles;
124774
125498
  exports.findCompositionCycles = findCompositionCycles;
124775
125499
  exports.findTimezone = findTimezone;
@@ -124834,6 +125558,7 @@ exports.inferExpressionKinds = inferExpressionKinds;
124834
125558
  exports.inferModelProvider = inferModelProvider;
124835
125559
  exports.ingestReading = ingestReading;
124836
125560
  exports.initialPoolMemoryState = initialPoolMemoryState;
125561
+ exports.insertComponent = insertComponent;
124837
125562
  exports.integralCadenceMs = require_composition.integralCadenceMs;
124838
125563
  exports.integralDraft = integralDraft;
124839
125564
  exports.integrateReading = require_composition.integrateReading;
@@ -125013,6 +125738,7 @@ exports.motionDetectionCapability = motionDetectionCapability;
125013
125738
  exports.motionSourceLabel = motionSourceLabel;
125014
125739
  exports.motionTriggerCapability = motionTriggerCapability;
125015
125740
  exports.motionZonesCapability = motionZonesCapability;
125741
+ exports.moveGroup = moveGroup;
125016
125742
  exports.mqttBrokerCapability = mqttBrokerCapability;
125017
125743
  exports.nativeObjectDetectionCapability = nativeObjectDetectionCapability;
125018
125744
  exports.navigationCapability = navigationCapability;
@@ -125191,12 +125917,16 @@ exports.recordingOnboardCapability = recordingOnboardCapability;
125191
125917
  exports.recordingSignalCapability = recordingSignalCapability;
125192
125918
  exports.recordsCapability = recordsCapability;
125193
125919
  exports.recordsErrorFromMessage = require_sleep.recordsErrorFromMessage;
125920
+ exports.rectContains = rectContains;
125921
+ exports.rectsOverlap = rectsOverlap;
125194
125922
  exports.rectsToCells = rectsToCells;
125195
125923
  exports.reducePoints = reducePoints;
125196
125924
  exports.reducerForActive = reducerForActive;
125197
125925
  exports.registerKnownSecret = require_sleep.registerKnownSecret;
125198
125926
  exports.remapCropDetections = remapCropDetections;
125199
125927
  exports.remoteCapability = remoteCapability;
125928
+ exports.remoteLayoutComponentsCapability = remoteLayoutComponentsCapability;
125929
+ exports.removeGroup = removeGroup;
125200
125930
  exports.removeScalarTarget = removeScalarTarget;
125201
125931
  exports.renameAlarmProfile = renameAlarmProfile;
125202
125932
  exports.renderVocabPhrase = renderVocabPhrase;
@@ -125452,6 +126182,7 @@ exports.transformSecretWrite = require_sleep.transformSecretWrite;
125452
126182
  exports.tryConvertDelta = tryConvertDelta;
125453
126183
  exports.tryConvertUnit = tryConvertUnit;
125454
126184
  exports.turnProviderCapability = turnProviderCapability;
126185
+ exports.ungroup = ungroup;
125455
126186
  exports.unitCompanionField = unitCompanionField;
125456
126187
  exports.unitDimension = unitDimension;
125457
126188
  exports.unitsForDimension = unitsForDimension;