@camstack/types 1.2.447 → 1.2.448

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()
66620
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()
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,430 @@ 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, { role: "up" }),
83414
+ btn("left", 1, 0, { role: "left" }),
83415
+ btn("ok", 1, 1, {
83416
+ role: "ok",
83417
+ shape: "round",
83418
+ tone: "accent"
83419
+ }),
83420
+ btn("right", 1, 2, { role: "right" }),
83421
+ btn("down", 2, 1, { role: "down" })
83422
+ ]
83423
+ },
83424
+ rocker("core:rocker-volume-v", "Volume rocker", "Bilanciere volume", "rocker-v", "VOL", [btn("up", 0, 0, {
83425
+ role: "volume_up",
83426
+ shape: "pill"
83427
+ }), btn("down", 1, 0, {
83428
+ role: "volume_down",
83429
+ shape: "pill"
83430
+ })], {
83431
+ rows: 2,
83432
+ cols: 1
83433
+ }),
83434
+ rocker("core:rocker-channel-v", "Channel rocker", "Bilanciere canali", "rocker-v", "CH", [btn("up", 0, 0, {
83435
+ role: "channel_up",
83436
+ shape: "pill"
83437
+ }), btn("down", 1, 0, {
83438
+ role: "channel_down",
83439
+ shape: "pill"
83440
+ })], {
83441
+ rows: 2,
83442
+ cols: 1
83443
+ }),
83444
+ rocker("core:rocker-h", "Minus / plus rocker", "Bilanciere meno / più", "rocker-h", null, [btn("minus", 0, 0, {
83445
+ role: "custom:minus",
83446
+ label: "−",
83447
+ shape: "pill"
83448
+ }), btn("plus", 0, 1, {
83449
+ role: "custom:plus",
83450
+ label: "+",
83451
+ shape: "pill"
83452
+ })], {
83453
+ rows: 1,
83454
+ cols: 2
83455
+ }),
83456
+ {
83457
+ id: "core:keypad",
83458
+ name: "Numeric keypad",
83459
+ nameI18n: {
83460
+ en: "Numeric keypad",
83461
+ it: "Tastierino numerico"
83462
+ },
83463
+ icon: "grid-3x3",
83464
+ category: "numbers",
83465
+ size: {
83466
+ rows: 4,
83467
+ cols: 3
83468
+ },
83469
+ group: { frame: "keypad" },
83470
+ 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" })]
83471
+ },
83472
+ {
83473
+ id: "core:colour-keys",
83474
+ name: "Colour keys",
83475
+ nameI18n: {
83476
+ en: "Colour keys",
83477
+ it: "Tasti colorati"
83478
+ },
83479
+ icon: "palette",
83480
+ category: "colour",
83481
+ size: {
83482
+ rows: 1,
83483
+ cols: 4
83484
+ },
83485
+ group: null,
83486
+ buttons: [
83487
+ btn("red", 0, 0, {
83488
+ role: "custom:color_red",
83489
+ tone: "red"
83490
+ }),
83491
+ btn("green", 0, 1, {
83492
+ role: "custom:color_green",
83493
+ tone: "green"
83494
+ }),
83495
+ btn("yellow", 0, 2, {
83496
+ role: "custom:color_yellow",
83497
+ tone: "yellow"
83498
+ }),
83499
+ btn("blue", 0, 3, {
83500
+ role: "custom:color_blue",
83501
+ tone: "blue"
83502
+ })
83503
+ ]
83504
+ },
83505
+ {
83506
+ id: "core:media-transport",
83507
+ name: "Media transport",
83508
+ nameI18n: {
83509
+ en: "Media transport",
83510
+ it: "Controlli multimediali"
83511
+ },
83512
+ icon: "play",
83513
+ category: "media",
83514
+ size: {
83515
+ rows: 1,
83516
+ cols: 5
83517
+ },
83518
+ group: null,
83519
+ buttons: [
83520
+ btn("rewind", 0, 0, { role: "rewind" }),
83521
+ btn("play", 0, 1, { role: "play" }),
83522
+ btn("pause", 0, 2, { role: "pause" }),
83523
+ btn("forward", 0, 3, { role: "fast_forward" }),
83524
+ btn("stop", 0, 4, { role: "stop" })
83525
+ ]
83526
+ },
83527
+ {
83528
+ id: "core:apps-row",
83529
+ name: "App row",
83530
+ nameI18n: {
83531
+ en: "App row",
83532
+ it: "Riga di app"
83533
+ },
83534
+ icon: "app-window",
83535
+ category: "apps",
83536
+ size: {
83537
+ rows: 1,
83538
+ cols: 4
83539
+ },
83540
+ group: null,
83541
+ buttons: [
83542
+ 1,
83543
+ 2,
83544
+ 3,
83545
+ 4
83546
+ ].map((n) => btn(`app${n}`, 0, n - 1, { role: `custom:app_slot_${n}` }))
83547
+ },
83548
+ {
83549
+ id: "core:back-home-menu",
83550
+ name: "Back, Home, Menu",
83551
+ nameI18n: {
83552
+ en: "Back, Home, Menu",
83553
+ it: "Indietro, Home, Menu"
83554
+ },
83555
+ icon: "house",
83556
+ category: "navigation",
83557
+ size: {
83558
+ rows: 1,
83559
+ cols: 3
83560
+ },
83561
+ group: null,
83562
+ buttons: [
83563
+ btn("back", 0, 0, { role: "back" }),
83564
+ btn("home", 0, 1, { role: "home" }),
83565
+ btn("menu", 0, 2, { role: "menu" })
83566
+ ]
83567
+ },
83568
+ {
83569
+ id: "core:section-divider",
83570
+ name: "Section divider",
83571
+ nameI18n: {
83572
+ en: "Section divider",
83573
+ it: "Divisore di sezione"
83574
+ },
83575
+ icon: "separator-horizontal",
83576
+ category: "layout",
83577
+ size: {
83578
+ rows: 1,
83579
+ cols: 1
83580
+ },
83581
+ group: {
83582
+ frame: "divider",
83583
+ title: "Section"
83584
+ },
83585
+ buttons: []
83586
+ },
83587
+ {
83588
+ id: "core:panel",
83589
+ name: "Titled panel",
83590
+ nameI18n: {
83591
+ en: "Titled panel",
83592
+ it: "Pannello con titolo"
83593
+ },
83594
+ icon: "panel-top",
83595
+ category: "layout",
83596
+ size: {
83597
+ rows: 2,
83598
+ cols: 3
83599
+ },
83600
+ group: {
83601
+ frame: "panel",
83602
+ title: "Panel"
83603
+ },
83604
+ buttons: []
83605
+ }
83606
+ ];
83607
+ /** A built-in component by id (the presets compose from these). */
83608
+ function builtinRemoteComponent(id) {
83609
+ const found = BUILTIN_REMOTE_COMPONENTS.find((component) => component.id === id);
83610
+ if (found === void 0) throw new Error(`no built-in remote component "${id}"`);
83611
+ return found;
83612
+ }
83613
+ //#endregion
82959
83614
  //#region src/ir/ir-error.ts
82960
83615
  var IrCodecError = class extends Error {
82961
83616
  code;
@@ -96959,6 +97614,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
96959
97614
  access: "create",
96960
97615
  role: "admin"
96961
97616
  },
97617
+ "remoteLayoutComponents.listComponents": {
97618
+ capName: "remote-layout-components",
97619
+ capScope: "system",
97620
+ addonId: null,
97621
+ access: "view",
97622
+ role: "admin"
97623
+ },
96962
97624
  "runningState.getStatus": {
96963
97625
  capName: "running-state",
96964
97626
  capScope: "device",
@@ -99322,6 +99984,7 @@ var KNOWN_CAP_NAMES = [
99322
99984
  "recording-signal",
99323
99985
  "records",
99324
99986
  "remote",
99987
+ "remote-layout-components",
99325
99988
  "running-state",
99326
99989
  "scene-monitor",
99327
99990
  "script-runner",
@@ -99542,6 +100205,7 @@ var SYSTEM_CAP_NAMES = [
99542
100205
  "recording-export",
99543
100206
  "records",
99544
100207
  "remote",
100208
+ "remote-layout-components",
99545
100209
  "server-management",
99546
100210
  "settings-store",
99547
100211
  "smtp-provider",
@@ -103540,6 +104204,7 @@ var SYSTEM_CAP_VOCABULARY = {
103540
104204
  "recording-export": system("recording exports — requested from the timeline, not an automation step"),
103541
104205
  records: system(INTERNAL),
103542
104206
  remote: system("the per-device remote setup — authored in the Customize remote editor, sent through the remote UI"),
104207
+ "remote-layout-components": system("the palette of the remote template editor — read-only data, never actuated"),
103543
104208
  "server-management": system(CLUSTER),
103544
104209
  "settings-store": system(INTERNAL),
103545
104210
  "smtp-provider": system(INTERNAL),
@@ -105527,6 +106192,7 @@ var CAPABILITY_NAMES = {
105527
106192
  recordingSignal: "recording-signal",
105528
106193
  records: "records",
105529
106194
  remote: "remote",
106195
+ remoteLayoutComponents: "remote-layout-components",
105530
106196
  runningState: "running-state",
105531
106197
  sceneMonitor: "scene-monitor",
105532
106198
  scriptRunner: "script-runner",
@@ -106166,6 +106832,10 @@ var CAPABILITY_ROUTER_KEYS = [
106166
106832
  key: "remote",
106167
106833
  name: "remote"
106168
106834
  },
106835
+ {
106836
+ key: "remoteLayoutComponents",
106837
+ name: "remote-layout-components"
106838
+ },
106169
106839
  {
106170
106840
  key: "runningState",
106171
106841
  name: "running-state"
@@ -106501,6 +107171,7 @@ var ALL_CAPABILITY_DEFINITIONS = [
106501
107171
  recordingSignalCapability,
106502
107172
  recordsCapability,
106503
107173
  remoteCapability,
107174
+ remoteLayoutComponentsCapability,
106504
107175
  runningStateCapability,
106505
107176
  sceneMonitorCapability,
106506
107177
  scriptRunnerCapability,
@@ -111291,6 +111962,7 @@ function createSystemProxy(api) {
111291
111962
  sendCommand: (input) => dispatch("remote", "sendCommand", "mutation", input),
111292
111963
  sendMediaCommand: (input) => dispatch("remote", "sendMediaCommand", "mutation", input)
111293
111964
  },
111965
+ remoteLayoutComponents: { listComponents: (input) => dispatch("remoteLayoutComponents", "listComponents", "query", input) },
111294
111966
  serverManagement: {
111295
111967
  getServerPackageStatus: (input) => dispatch("serverManagement", "getServerPackageStatus", "query", input),
111296
111968
  checkServerUpdate: (input) => dispatch("serverManagement", "checkServerUpdate", "mutation", input),
@@ -115526,8 +116198,6 @@ var NC_TEMPLATE_VARS = [
115526
116198
  * delete it from here then, and the served catalog names it again.
115527
116199
  */
115528
116200
  var NC_TEMPLATE_HELD_BACK_SYSTEM_EVENT_KINDS = [
115529
- "node-restarting",
115530
- "node-updating",
115531
116201
  "alert-critical",
115532
116202
  "alert-error",
115533
116203
  "alert-critical-resolved",
@@ -122371,6 +123041,7 @@ exports.BENCH_ROOT_STEP_ID = BENCH_ROOT_STEP_ID;
122371
123041
  exports.BINARY_AGGREGATES = require_composition.BINARY_AGGREGATES;
122372
123042
  exports.BIRTH_DECISION_DEFAULT_LIMIT = BIRTH_DECISION_DEFAULT_LIMIT;
122373
123043
  exports.BOOT_RECOVERY_BACKOFF_MS = require_sleep.BOOT_RECOVERY_BACKOFF_MS;
123044
+ exports.BUILTIN_REMOTE_COMPONENTS = BUILTIN_REMOTE_COMPONENTS;
122374
123045
  exports.BUILTIN_RESULT_KINDS = BUILTIN_RESULT_KINDS;
122375
123046
  exports.BackgroundRunRegistry = BackgroundRunRegistry;
122376
123047
  exports.BacklightModeSchema = BacklightModeSchema;
@@ -123099,6 +123770,7 @@ exports.KeychainWriteRefusedError = require_sleep.KeychainWriteRefusedError;
123099
123770
  exports.LABEL_SUPPLY_MACRO = LABEL_SUPPLY_MACRO;
123100
123771
  exports.LABEL_SUPPLY_STEP_IDS = LABEL_SUPPLY_STEP_IDS;
123101
123772
  exports.LABEL_SUPPLY_VOCABULARIES = LABEL_SUPPLY_VOCABULARIES;
123773
+ exports.LAYOUT_EDIT_REFUSALS = LAYOUT_EDIT_REFUSALS;
123102
123774
  exports.LEGACY_READ_ONLY_CONFIG_KEY = LEGACY_READ_ONLY_CONFIG_KEY;
123103
123775
  exports.LIGHT_SHARED_CAPS = LIGHT_SHARED_CAPS;
123104
123776
  exports.LINKED_DEVICES_SLOT = LINKED_DEVICES_SLOT;
@@ -123205,6 +123877,7 @@ exports.MAX_BIRTH_LATENCY_PROXY_MS = MAX_BIRTH_LATENCY_PROXY_MS;
123205
123877
  exports.MAX_CAMERA_SENSORS = MAX_CAMERA_SENSORS;
123206
123878
  exports.MAX_CLIP_EVENT_IDS = MAX_CLIP_EVENT_IDS;
123207
123879
  exports.MAX_CLIP_LABELS = MAX_CLIP_LABELS;
123880
+ exports.MAX_COMPONENT_BUTTONS = MAX_COMPONENT_BUTTONS;
123208
123881
  exports.MAX_COMPOSITION_CODE_LENGTH = require_composition.MAX_COMPOSITION_CODE_LENGTH;
123209
123882
  exports.MAX_COMPOSITION_FEATURES = require_composition.MAX_COMPOSITION_FEATURES;
123210
123883
  exports.MAX_COMPOSITION_FIELDS_PER_FEATURE = require_composition.MAX_COMPOSITION_FIELDS_PER_FEATURE;
@@ -123218,6 +123891,7 @@ exports.MAX_EXPRESSION_EVAL_STEPS = require_composition.MAX_EXPRESSION_EVAL_STEP
123218
123891
  exports.MAX_EXPRESSION_SOURCE_LENGTH = require_composition.MAX_EXPRESSION_SOURCE_LENGTH;
123219
123892
  exports.MAX_HELPER_MEMBERS = require_composition.MAX_HELPER_MEMBERS;
123220
123893
  exports.MAX_KEYS = MAX_KEYS;
123894
+ exports.MAX_LAYOUT_GROUPS = MAX_LAYOUT_GROUPS;
123221
123895
  exports.MAX_LAYOUT_SPAN = MAX_LAYOUT_SPAN;
123222
123896
  exports.MAX_REASONS_PER_KEY = MAX_REASONS_PER_KEY;
123223
123897
  exports.MAX_REMOTE_BINDINGS = MAX_REMOTE_BINDINGS;
@@ -123786,8 +124460,12 @@ exports.RECOVERY_VIEW_PX = RECOVERY_VIEW_PX;
123786
124460
  exports.REDACTED_SECRET = require_sleep.REDACTED_SECRET;
123787
124461
  exports.REFERENCE_COMPUTE = REFERENCE_COMPUTE;
123788
124462
  exports.REFERENCE_MATCH_FRACTION = REFERENCE_MATCH_FRACTION;
124463
+ exports.REMOTE_BUTTON_SHAPES = REMOTE_BUTTON_SHAPES;
124464
+ exports.REMOTE_BUTTON_TONES = REMOTE_BUTTON_TONES;
124465
+ exports.REMOTE_COMPONENT_CATEGORIES = REMOTE_COMPONENT_CATEGORIES;
123789
124466
  exports.REMOTE_CONFIG_REFUSALS = REMOTE_CONFIG_REFUSALS;
123790
124467
  exports.REMOTE_CUSTOM_ROLE_PREFIX = REMOTE_CUSTOM_ROLE_PREFIX;
124468
+ exports.REMOTE_GROUP_FRAMES = REMOTE_GROUP_FRAMES;
123791
124469
  exports.REMOTE_KNOWN_ROLES = REMOTE_KNOWN_ROLES;
123792
124470
  exports.REMOTE_MAX_REPEAT = REMOTE_MAX_REPEAT;
123793
124471
  exports.REMOTE_MEDIA_COMMANDS = REMOTE_MEDIA_COMMANDS;
@@ -123906,10 +124584,17 @@ exports.RemoteAssumedStateSchema = RemoteAssumedStateSchema;
123906
124584
  exports.RemoteBindingActionSchema = RemoteBindingActionSchema;
123907
124585
  exports.RemoteBindingSchema = RemoteBindingSchema;
123908
124586
  exports.RemoteBoundCommandSchema = RemoteBoundCommandSchema;
124587
+ exports.RemoteButtonShapeSchema = RemoteButtonShapeSchema;
124588
+ exports.RemoteButtonToneSchema = RemoteButtonToneSchema;
123909
124589
  exports.RemoteCommandListSchema = RemoteCommandListSchema;
124590
+ exports.RemoteComponentButtonSchema = RemoteComponentButtonSchema;
124591
+ exports.RemoteComponentCategorySchema = RemoteComponentCategorySchema;
123910
124592
  exports.RemoteConfigSchema = RemoteConfigSchema;
123911
124593
  exports.RemoteCustomButtonSchema = RemoteCustomButtonSchema;
124594
+ exports.RemoteGroupFrameSchema = RemoteGroupFrameSchema;
123912
124595
  exports.RemoteLayoutButtonSchema = RemoteLayoutButtonSchema;
124596
+ exports.RemoteLayoutComponentSchema = RemoteLayoutComponentSchema;
124597
+ exports.RemoteLayoutGroupSchema = RemoteLayoutGroupSchema;
123913
124598
  exports.RemoteLayoutPayloadSchema = RemoteLayoutPayloadSchema;
123914
124599
  exports.RemoteMediaCommandResultSchema = RemoteMediaCommandResultSchema;
123915
124600
  exports.RemoteMediaCommandSchema = RemoteMediaCommandSchema;
@@ -124481,6 +125166,7 @@ exports.buildTriggerTrace = buildTriggerTrace;
124481
125166
  exports.buildVideoArgs = require_composition.buildVideoArgs;
124482
125167
  exports.buildViewerLink = buildViewerLink;
124483
125168
  exports.builtCapabilityBlockers = builtCapabilityBlockers;
125169
+ exports.builtinRemoteComponent = builtinRemoteComponent;
124484
125170
  exports.bundledCamstack = bundledCamstack;
124485
125171
  exports.buttonCapability = buttonCapability;
124486
125172
  exports.calendarEventLocalSpan = calendarEventLocalSpan;
@@ -124770,6 +125456,7 @@ exports.fieldReads = require_composition.fieldReads;
124770
125456
  exports.fieldUnitKeys = fieldUnitKeys;
124771
125457
  exports.fieldsPinnedAtValueBelow = fieldsPinnedAtValueBelow;
124772
125458
  exports.filesystemBrowseCapability = filesystemBrowseCapability;
125459
+ exports.fillWidthScale = fillWidthScale;
124773
125460
  exports.findCandidateCycles = findCandidateCycles;
124774
125461
  exports.findCompositionCycles = findCompositionCycles;
124775
125462
  exports.findTimezone = findTimezone;
@@ -124834,6 +125521,7 @@ exports.inferExpressionKinds = inferExpressionKinds;
124834
125521
  exports.inferModelProvider = inferModelProvider;
124835
125522
  exports.ingestReading = ingestReading;
124836
125523
  exports.initialPoolMemoryState = initialPoolMemoryState;
125524
+ exports.insertComponent = insertComponent;
124837
125525
  exports.integralCadenceMs = require_composition.integralCadenceMs;
124838
125526
  exports.integralDraft = integralDraft;
124839
125527
  exports.integrateReading = require_composition.integrateReading;
@@ -125013,6 +125701,7 @@ exports.motionDetectionCapability = motionDetectionCapability;
125013
125701
  exports.motionSourceLabel = motionSourceLabel;
125014
125702
  exports.motionTriggerCapability = motionTriggerCapability;
125015
125703
  exports.motionZonesCapability = motionZonesCapability;
125704
+ exports.moveGroup = moveGroup;
125016
125705
  exports.mqttBrokerCapability = mqttBrokerCapability;
125017
125706
  exports.nativeObjectDetectionCapability = nativeObjectDetectionCapability;
125018
125707
  exports.navigationCapability = navigationCapability;
@@ -125191,12 +125880,16 @@ exports.recordingOnboardCapability = recordingOnboardCapability;
125191
125880
  exports.recordingSignalCapability = recordingSignalCapability;
125192
125881
  exports.recordsCapability = recordsCapability;
125193
125882
  exports.recordsErrorFromMessage = require_sleep.recordsErrorFromMessage;
125883
+ exports.rectContains = rectContains;
125884
+ exports.rectsOverlap = rectsOverlap;
125194
125885
  exports.rectsToCells = rectsToCells;
125195
125886
  exports.reducePoints = reducePoints;
125196
125887
  exports.reducerForActive = reducerForActive;
125197
125888
  exports.registerKnownSecret = require_sleep.registerKnownSecret;
125198
125889
  exports.remapCropDetections = remapCropDetections;
125199
125890
  exports.remoteCapability = remoteCapability;
125891
+ exports.remoteLayoutComponentsCapability = remoteLayoutComponentsCapability;
125892
+ exports.removeGroup = removeGroup;
125200
125893
  exports.removeScalarTarget = removeScalarTarget;
125201
125894
  exports.renameAlarmProfile = renameAlarmProfile;
125202
125895
  exports.renderVocabPhrase = renderVocabPhrase;
@@ -125452,6 +126145,7 @@ exports.transformSecretWrite = require_sleep.transformSecretWrite;
125452
126145
  exports.tryConvertDelta = tryConvertDelta;
125453
126146
  exports.tryConvertUnit = tryConvertUnit;
125454
126147
  exports.turnProviderCapability = turnProviderCapability;
126148
+ exports.ungroup = ungroup;
125455
126149
  exports.unitCompanionField = unitCompanionField;
125456
126150
  exports.unitDimension = unitDimension;
125457
126151
  exports.unitsForDimension = unitsForDimension;