@lumiastream/lumia-types 3.9.3 → 3.9.4

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.
@@ -25,7 +25,7 @@ Let's get started:
25
25
  **System bases** (built into Lumia):
26
26
  `delay, lumia, overlay, api, commandRunner, inputEvents`
27
27
 
28
- > Note: `lumia` and `overlay` also accept the aliases `lumiaActions` and `overlayActions`, and the input base is `inputEvents` (plural).
28
+ > Note: the canonical system bases are `lumia` and `overlay`. The older spellings `lumiaActions` and `overlayActions` still run but are **deprecated** — prefer `lumia` / `overlay`. The input base is `inputEvents` (plural).
29
29
 
30
30
  **Integration bases** — every connected integration is also a valid base. These include (and more are added over time):
31
31
  `twitch, youtube, facebook, tiktok, kick, discord, obs, slobs, meld, spotify, youtubemusic, nowplaying, vlc, voicemod, streamerbot, vtubestudio, midi, osc, artnet, mqtt, serial, websocket, broadlink, hue, lifx, nanoleaf, govee, wled, wiz, tplink, tuya, yeelight, elgato, streamdeck, touchportal, loupedeck, homeassistant, switchbot` plus any installed plugin (use the plugin's id as the base).
@@ -36,7 +36,27 @@ Let's get started:
36
36
 
37
37
  `variables`: allows you to send in different variables for each action. But do note that the variables that are already on the command/alert will also be spread on to this variables object. Variables are not required
38
38
 
39
- There are more fields that are sometimes used in actions. `delay` (a number in milliseconds) can be added to any action to wait before running it.
39
+ To pause between actions, insert a dedicated **delay step**: an entry whose `type` is `"delay"` with the milliseconds in `delay` (e.g. `{ type: "delay", delay: 1000 }`). It can carry any `base` and runs in order with the rest of the list. (An inline `delay` on a non-delay action is **not** applied by the runner — use a delay step.)
40
+
41
+ #### Where actions live: the unified `actions` lane
42
+
43
+ Inside a Lumia **command** or **alert**, every action — whatever its `base` (a `lumia` system action, an `overlay` action, a `twitch` action, an integration, a plugin) — lives together in one ordered list on the command:
44
+
45
+ ```json
46
+ {
47
+ "actions": {
48
+ "before": [{ "base": "lumia", "type": "tts", "value": { "message": "Hi" } }],
49
+ "after": [{ "base": "twitch", "type": "clip", "value": { "title": "Clutch!", "duration": 30 } }],
50
+ "waitForActions": true
51
+ }
52
+ }
53
+ ```
54
+
55
+ - **`before`** runs before the command/alert's main effect (its light / overlay reaction); **`after`** runs after it.
56
+ - **`waitForActions`** (optional): when `true`, each action is awaited so the list runs in strict order before the command continues; when omitted or `false`, the list fires without waiting.
57
+ - Every entry is one action object of the shape `{ base, type, value?, delay?, args? }` — the same object you pass to the custom-code `actions()` helper. `delay` holds the milliseconds for a `type: "delay"` step; `args` carries the extra payload used by imported Streamer.bot actions.
58
+
59
+ This unified `actions` lane is the current model, and the one the AI action creator and the Streamer.bot / Mix It Up importers target. It **replaces** the older per-source command fields (`lumiaActions`, `overlayActions`, `api`, `commandRunner`, `inputEvents`) and the per-integration lanes (`command.<integration>.before/after` — e.g. `command.wavelink`, `command.streamfog`, whose entries have no `base`), all of which are **deprecated** — within the unified lane the source is simply each entry's `base`.
40
60
 
41
61
  ### Passing an array of actions
42
62
 
@@ -63,9 +83,11 @@ These are the built-in `type` values for each system base.
63
83
 
64
84
  **`base: "commandRunner"`** — `app/file, shell command, delay`
65
85
 
66
- **`base: "inputEvents"`** — `keyboard, mouse, delay`
86
+ **`base: "inputEvents"`** — `keyboard, mouse, delay`. The key/mouse data is **flat on the action**, not under `value`:
87
+ > - `keyboard`: `{ base: "inputEvents", type: "keyboard", keyboardValue: { value: "ctrl+j", valueType: "combination" } }` — `valueType` is `combination` (a hotkey like `ctrl+shift+f5`) or `input` (type the literal `value` as text); optional `longPress`, `cpm`.
88
+ > - `mouse`: `{ base: "inputEvents", type: "mouse", mouseValue: { x: 0, y: 100, clickEvent: "left", moveType: "set" } }` — `clickEvent` is `left` / `right` / `none`; optional `x1`, `y1`, `doubleClick`, `mouseSpeed`.
67
89
 
68
- **`base: "delay"`**no `type`; pass the millisecond delay directly as the value, e.g. `{ base: "delay", value: 1000 }`.
90
+ **delay step** an entry with `type: "delay"` pauses the list; put the milliseconds in `delay` (e.g. `{ type: "delay", delay: 1000 }`). `duration` is accepted in place of `delay`, and the step runs under any `base`.
69
91
 
70
92
  > Tip: most of the `lumia` and `overlay` actions already have dedicated helper functions (`tts`, `chatbot`, `overlaySetTextContent`, etc.) in `helper-functions.md`. Reach for `actions()` mainly when you need an integration action that does not have a helper yet.
71
93
 
@@ -460,6 +482,8 @@ async function() {
460
482
  }
461
483
  ```
462
484
 
485
+ > MIDI device targeting: `port` (the device's port number) is the simplest selector and is required. On a multi-device setup — where ports can shift between sessions — also pass the optional `deviceId` and/or `deviceName`; Lumia resolves the target by `deviceId`, then `deviceName`, then `port`. `type` is `note-on`, `note-off`, or `delay` (a `delay` entry just waits its `delay` ms).
486
+
463
487
  #### Twitch (`base: "twitch"`)
464
488
 
465
489
  Twitch actions take a `value` object. Most string inputs — usernames, message ids, comma-separated option lists — go in `value.message`. Each action needs the matching Twitch permission to be granted on your connection.
@@ -569,7 +593,23 @@ async function() {
569
593
  }
570
594
  ```
571
595
 
572
- Convenience source types (Lumia builds the `inputSettings` for you): `SetSourceText` (`{ inputName, text }`), `SetGenericUrlSource` / `SetSourceUrl` (`{ inputName, url }`), `SetGenericFileSource` / `SetImageFileSource` / `SetMediaFileSource` (`{ inputName, file }`). All other request-types match the OBS v5 API exactly.
596
+ Common direct request-types (flat fields, verified against real setups):
597
+
598
+ | `request-type` | fields |
599
+ | --- | --- |
600
+ | `SetCurrentProgramScene` | `{ sceneName }` |
601
+ | `SetSceneItemEnabled` | `{ sceneName, inputName, sceneItemEnabled }` — Lumia resolves `inputName` → the scene-item id |
602
+ | `SetSourceFilterEnabled` | `{ sourceName, filterName, filterEnabled }` |
603
+ | `SaveSourceScreenshot` | `{ sourceName, imageFormat, imageFilePath }` |
604
+ | `StartStream` / `StopStream` / `StartRecord` / `StopRecord` | no extra fields |
605
+
606
+ Convenience source types let you set a source's content without hand-building `inputSettings`, but they only work if you **also** pass `lumiaOrigin: "SetInputSettings"` — Lumia rewrites the request into a real `SetInputSettings` call using it. Without `lumiaOrigin` the alias is sent verbatim and OBS rejects it. The aliases: `SetSourceText` (`{ inputName, text }`), `SetGenericUrlSource` / `SetSourceUrl` (`{ inputName, url }`), `SetGenericFileSource` / `SetImageFileSource` / `SetMediaFileSource` (`{ inputName, file }`):
607
+
608
+ ```js
609
+ { base: "obs", "request-type": "SetMediaFileSource", lumiaOrigin: "SetInputSettings", inputName: "Replay", file: "{{get_latest_file_from_folder=G:/Replays}}" }
610
+ ```
611
+
612
+ For custom code it's usually simpler to skip the alias and call `sendRawObsJson({ "request-type": "SetInputSettings", inputName: "Replay", inputSettings: { local_file: "..." } })` directly. All other request-types match the OBS v5 API exactly.
573
613
 
574
614
  #### Streamlabs Desktop (`base: "slobs"`)
575
615
 
@@ -657,20 +697,20 @@ These read their fields **flat** on the action object (no `value` wrapper).
657
697
 
658
698
  ```js
659
699
  async function() {
660
- // MQTT: publish to a topic on a configured broker (host = the broker's host)
661
- await actions([{ base: "mqtt", type: "send", host: "192.168.1.50", topic: "home/light", value: "ON" }]);
700
+ // MQTT: publish to a topic. host is the broker URL mqtt://<host>:<port>
701
+ await actions([{ base: "mqtt", type: "send", host: "mqtt://192.168.1.50:1883", topic: "home/light", value: "ON" }]);
662
702
 
663
- // WebSocket: send to one connection by its id
703
+ // WebSocket: send to one configured connection by its id
664
704
  await actions([{ base: "websocket", type: "data", device: "<websocketId>", value: "hello" }]);
665
705
 
666
- // Serial: write raw data to a port (9600 baud)
706
+ // Serial: write raw data to a port (COM3 on Windows, /dev/tty.* on macOS)
667
707
  await actions([{ base: "serial", type: "write", port: "COM3", value: "L1\n" }]);
668
708
 
669
- // Art-Net: set DMX channels on a universe (values 0-255; no template tokens here)
670
- await actions([{ base: "artnet", type: "artnet", universe: 0, values: [{ channel: 1, value: 255 }, { channel: 2, value: 128 }] }]);
709
+ // Art-Net: set DMX channels on a universe (channel 1-512, value 0-255). universe is a string
710
+ await actions([{ base: "artnet", type: "artnet", universe: "0", values: [{ channel: 1, value: 255 }, { channel: 2, value: 128 }] }]);
671
711
 
672
- // Broadlink: send a learned IR/RF code by its library id or name to a device
673
- await actions([{ base: "broadlink", type: "ir", device: "<deviceId>", value: "TV Power" }]);
712
+ // Broadlink: send a learned IR/RF code by its library id to a device
713
+ await actions([{ base: "broadlink", type: "ir", device: "<deviceId>", value: "<codeId>" }]);
674
714
  done();
675
715
  }
676
716
  ```
@@ -682,11 +722,35 @@ All take a `value` object.
682
722
  | base | type | `value` | notes |
683
723
  | --- | --- | --- | --- |
684
724
  | `switchbot` | `turnOn` / `turnOff` / `toggle` | `{ deviceId: "<id>" }` | |
725
+ | `wavelink` | `FILTER_SET` | `{ filterId: "<uuid>", isEnabled: true, inputId: "Wave Link Mic In 1" }` | enable/disable an input's filter; `filterId: "ALL_EFFECTS"` targets every effect on that input |
685
726
  | `wavelink` | `SET_INPUT_VOLUME` | `{ inputId: "<id>", volume: 50, mixerID: "com.elgato.mix.local" }` | volume 0–100; mixer `local`/`stream`/`all` |
686
727
  | `wavelink` | `MUTE_INPUT` | `{ inputId: "<id>", muted: true, mixerID: "com.elgato.mix.local" }` | |
687
728
  | `wavelink` | `SET_OUTPUT_VOLUME` / `MUTE_OUTPUT` | `{ volume: 50, mixerID: "com.elgato.mix.local" }` | |
688
729
  | `wavelink3` | same types as `wavelink` | same shapes | Wave Link 3.0 uses base `wavelink3`; some legacy filter/mic types are unsupported on v3 |
689
- | `camerahub` | `SET_WEBCAM_PROPERTY` | `{ propertyID: "Brightness", propertyValue: 60 }` | also `SELECT_DEVICE`, `SET_NVIDIA_VIDEO_EFFECT`, `SET_LUT_EFFECT`, `SET_PROMPTER_PROPERTY` |
730
+ | `camerahub` | `SET_WEBCAM_PROPERTY` | `{ propertyID: "Brightness", propertyValue: 60 }` | all camerahub types share one property-bag `value` (`deviceID, propertyID, propertyValue, intensity, isEnabled, …`) — set only the fields the type uses. Also `SELECT_DEVICE`, `SET_NVIDIA_VIDEO_EFFECT`, `SET_LUT_EFFECT` (`intensity`), `SET_PROMPTER_PROPERTY` |
690
731
  | `streamfog` | `activateLens` | `{ lensId: "<id or name>", duration: 10 }` | `lensId: "random"` picks one; also `activateOutfit` (`{ outfit, duration }`) and `disableLens` (`{}`) |
691
732
 
733
+ #### Plugin actions (`base: "<pluginId>"`)
734
+
735
+ Every installed plugin is also a valid base — use the plugin's id as the `base`, plus the plugin's own action `type` and a `value` object of that plugin's fields. The exact `type` and `value` come from the plugin's manifest, so the reliable way to discover them is to add the action once in the editor and read it back; the shape is always `{ base: "<pluginId>", type: "<action type>", value: { ...plugin fields } }`. Verified examples:
736
+
737
+ ```js
738
+ async function() {
739
+ // Tapo smart plug — target is the device IP, or "IP|deviceId"
740
+ await actions([{ base: "tapo_plugin", type: "tapo_turn_on", value: { target: "192.168.50.169" } }]);
741
+
742
+ // Steam — look up a game
743
+ await actions([{ base: "steam", type: "fetch_game", value: { game: "Sonic" } }]);
744
+
745
+ // ClickUp — create a task (assigneeId is comma-separated)
746
+ await actions([{ base: "clickup", type: "create_task", value: { name: "{{message}}", assigneeId: "14928785,14928776" } }]);
747
+
748
+ // ElevenLabs TTS
749
+ await actions([{ base: "elevenlabs_tts", type: "speak", value: { message: "{{message}}", voiceId: "hpp4J3VqNfWAUOO0d1Us", modelId: "eleven_multilingual_v2", volume: 100 } }]);
750
+ done();
751
+ }
752
+ ```
753
+
754
+ A plugin action is silently skipped unless its plugin is installed, connected, and enabled.
755
+
692
756
  This documentation can get extremely broad for every integration, so if you get stuck please visit our [**Discord**](https://discord.gg/R8rCaKb) to ask us any questions.
@@ -110,12 +110,27 @@ Include these two helper aliases in your embeddings so the model can quickly map
110
110
  "min": { "type": "number" },
111
111
  "max": { "type": "number" },
112
112
  "visibleIf": {
113
- "type": "object",
114
- "required": ["key", "equals"],
115
- "properties": {
116
- "key": { "type": "string" },
117
- "equals": {}
118
- }
113
+ "oneOf": [
114
+ {
115
+ "type": "object",
116
+ "required": ["key", "equals"],
117
+ "properties": {
118
+ "key": { "type": "string" },
119
+ "equals": {}
120
+ }
121
+ },
122
+ {
123
+ "type": "array",
124
+ "items": {
125
+ "type": "object",
126
+ "required": ["key", "equals"],
127
+ "properties": {
128
+ "key": { "type": "string" },
129
+ "equals": {}
130
+ }
131
+ }
132
+ }
133
+ ]
119
134
  },
120
135
  "hidden": { "type": "boolean" }
121
136
  },
@@ -399,7 +399,7 @@ A field object can now contain these properties:
399
399
  | **`value`** | ❌ | Default value that appears the first time the user opens the overlay (also pre populates `Overlay.data`). Omit it to leave the field blank/unchecked on first load. | `"value": 18` |
400
400
  | **`options`** | ☑️\* | Key value map of selectable choices. Required **only** for `dropdown`, `multiselect` and `slider`; ignored for other types. For `slider`, `options` supports `step`, `min`, `max`, `prefix`, `suffix`. | `"options": { "step": 5, "min": 0, "max": 100 }` |
401
401
  | **`order`** | ❌ | **Display order priority**. Fields with lower numbers appear first. Fields without `order` appear after ordered fields, sorted alphabetically by key. | `"order": 1` |
402
- | **`visibleIf`** | ❌ | **Conditional render rule**. Field is shown **only if** `Overlay.data[visibleIf.key]` strictly equals one of the values in `visibleIf.equals`. | `"visibleIf": { "key": "targetKey", "equals": ["yes", "maybe"] }` |
402
+ | **`visibleIf`** | ❌ | **Conditional render rule**. Field is shown **only if** `Overlay.data[visibleIf.key]` strictly equals one of the values in `visibleIf.equals`. Pass an **array of rules** to require that **every** rule matches (AND across keys). | `"visibleIf": [{ "key": "targetKey", "equals": ["yes", "maybe"] }, { "key": "mode", "equals": "advanced" }]` |
403
403
  | **`hidden`** | ❌ | **Hard-hide rule.** When set to `true`, the field is **never displayed** in the Configs sidebar, preventing end users from altering it. The value still flows into `Overlay.data`, so the overlay can rely on it internally.<br>Useful for locking event subscriptions or other advanced settings. | `"hidden": true` |
404
404
 
405
405
  Additional properties for text fields (`type: "input"` and `type: "textarea"`):
@@ -761,10 +761,13 @@ In this example, the fields would appear in this order:
761
761
  ### Visible If Conditional Fields
762
762
 
763
763
  The `visibleIf` field is a conditional statement that determines whether a field should be visible or not based on a condition.
764
- The `visibleIf` field is a JSON object with two keys: `key` and `equals`.
764
+ The `visibleIf` field can be **either**:
765
+
766
+ - a single rule object with two keys, `key` and `equals`, or
767
+ - an **array of rule objects**, in which case the field is shown only when **every** rule matches (logical AND). Use the array form to gate a field on more than one key at once.
765
768
 
766
769
  The `key` key is a string that represents the name of the field whose value should be used in the conditional statement.
767
- The `equals` key is a string, number, boolean, or an array that represents the value of the field whose value should be used in the conditional statement.
770
+ The `equals` key is a string, number, boolean, or an array that represents the value(s) of that field that make the condition match. When `equals` is an array, the rule matches if the field's value is one of the listed values (and for `multiselect` fields, if any selected value is in the list).
768
771
 
769
772
  #### Single Primitive Equals Example
770
773
 
@@ -804,6 +807,35 @@ The `equals` key is a string, number, boolean, or an array that represents the v
804
807
  }
805
808
  ```
806
809
 
810
+ #### Multiple Keys (Array of Rules) Example
811
+
812
+ Pass an array of rule objects to require that **every** rule matches before the
813
+ field shows. Here `subcolor` only appears when **both** `color` and `alerts`
814
+ are set to one of `red`, `blue`, or `green`:
815
+
816
+ ```json
817
+ {
818
+ "color": {
819
+ "type": "input",
820
+ "label": "Colors"
821
+ },
822
+ "subcolor": {
823
+ "type": "input",
824
+ "label": "Subcolors",
825
+ "visibleIf": [
826
+ {
827
+ "key": "color",
828
+ "equals": ["red", "blue", "green"]
829
+ },
830
+ {
831
+ "key": "alerts",
832
+ "equals": ["red", "blue", "green"]
833
+ }
834
+ ]
835
+ }
836
+ }
837
+ ```
838
+
807
839
  ---
808
840
 
809
841
  ## 📊 Data Tab
@@ -53,7 +53,7 @@ export enum ConfigsFieldType {
53
53
  }
54
54
 
55
55
  /**
56
- * Conditional render rule for a config field.
56
+ * A single conditional render rule for a config field.
57
57
  * The field is shown only when `Overlay.data[key]` strictly equals one of `equals`
58
58
  * (scalar) or intersects with `equals` (when either side is an array).
59
59
  *
@@ -68,6 +68,23 @@ export interface ConfigVisibleIf {
68
68
  equals: string | number | boolean | Array<string | number | boolean>;
69
69
  }
70
70
 
71
+ /**
72
+ * Conditional render rule, or a list of rules. When an array is supplied, the
73
+ * field is shown only when **every** rule matches (logical AND), letting you
74
+ * gate a field on several keys at once.
75
+ *
76
+ * @example
77
+ * // single key
78
+ * { key: 'mode', equals: 'advanced' }
79
+ * @example
80
+ * // multiple keys — all must match
81
+ * [
82
+ * { key: 'color', equals: ['red', 'blue', 'green'] },
83
+ * { key: 'alerts', equals: ['red', 'blue', 'green'] },
84
+ * ]
85
+ */
86
+ export type ConfigVisibleIfRule = ConfigVisibleIf | ConfigVisibleIf[];
87
+
71
88
  /** Properties common to every config field, regardless of `type`. */
72
89
  export interface BaseConfigField {
73
90
  /** Human-readable name shown next to the control in the Configs sidebar. */
@@ -79,9 +96,10 @@ export interface BaseConfigField {
79
96
  order?: number;
80
97
  /**
81
98
  * Conditional render rule. When set, the field renders only when
82
- * `Overlay.data[visibleIf.key]` matches `visibleIf.equals`.
99
+ * `Overlay.data[visibleIf.key]` matches `visibleIf.equals`. Pass an array of
100
+ * rules to require that **every** rule matches (logical AND across keys).
83
101
  */
84
- visibleIf?: ConfigVisibleIf;
102
+ visibleIf?: ConfigVisibleIfRule;
85
103
  /**
86
104
  * Hard-hide rule. When `true`, the field is never displayed in the Configs
87
105
  * sidebar, but its `value` still flows into `Overlay.data` for internal use.
@@ -35,7 +35,7 @@ Source of truth (in order): this file > `gpt-instructions-extended.md` (long-for
35
35
  - **HTML**: body content only. Stable IDs/classes. No inline `<script>` or `<style>`.
36
36
  - **CSS**: stylesheet only. Unquoted CSS variables for colors/sizes/numbers. Quoted only where CSS requires a string (`font-family: "{{font}}";`).
37
37
  - **JS**: top-level JS. Use `Overlay.data` for Data values. Use `textContent`/`createElement`/`appendChild` for any chat, alert, or user-generated text — never `innerHTML` with user input. `fetch` is allowed.
38
- - **Configs**: field types: `input`, `textarea`, `number`, `checkbox`, `dropdown`, `multiselect`, `colorpicker`, `fontpicker`, `slider`, `imageupload`, `soundupload`, `videoupload`, `actionbutton`. Required: `type`, `label`. Use `value` for defaults. `dropdown`/`multiselect` need `options`. `slider` needs `options.min`/`max` (usually `step`/`prefix`/`suffix`). `imageupload`/`soundupload`/`videoupload` accept an `accept` MIME hint. `actionbutton` has no `value` — bind it via `Overlay.on('configAction', ({ key }) => …)`. Use `order` to control sidebar order. Use `visibleIf` for conditional fields. Keys must be machine-friendly (no spaces).
38
+ - **Configs**: field types: `input`, `textarea`, `number`, `checkbox`, `dropdown`, `multiselect`, `colorpicker`, `fontpicker`, `slider`, `imageupload`, `soundupload`, `videoupload`, `actionbutton`. Required: `type`, `label`. Use `value` for defaults. `dropdown`/`multiselect` need `options`. `slider` needs `options.min`/`max` (usually `step`/`prefix`/`suffix`). `imageupload`/`soundupload`/`videoupload` accept an `accept` MIME hint. `actionbutton` has no `value` — bind it via `Overlay.on('configAction', ({ key }) => …)`. Use `order` to control sidebar order. Use `visibleIf` for conditional fields — either a single `{ key, equals }` rule or an array of rules (the field shows only when every rule matches). Keys must be machine-friendly (no spaces).
39
39
  - **Data**: every Configs key must have a matching Data key with a matching default. Data can hold internal values that aren't in Configs (rare).
40
40
 
41
41
  ## Variables
@@ -53,7 +53,7 @@ export enum ConfigsFieldType {
53
53
  }
54
54
 
55
55
  /**
56
- * Conditional render rule for a config field.
56
+ * A single conditional render rule for a config field.
57
57
  * The field is shown only when `Overlay.data[key]` strictly equals one of `equals`
58
58
  * (scalar) or intersects with `equals` (when either side is an array).
59
59
  *
@@ -68,6 +68,23 @@ export interface ConfigVisibleIf {
68
68
  equals: string | number | boolean | Array<string | number | boolean>;
69
69
  }
70
70
 
71
+ /**
72
+ * Conditional render rule, or a list of rules. When an array is supplied, the
73
+ * field is shown only when **every** rule matches (logical AND), letting you
74
+ * gate a field on several keys at once.
75
+ *
76
+ * @example
77
+ * // single key
78
+ * { key: 'mode', equals: 'advanced' }
79
+ * @example
80
+ * // multiple keys — all must match
81
+ * [
82
+ * { key: 'color', equals: ['red', 'blue', 'green'] },
83
+ * { key: 'alerts', equals: ['red', 'blue', 'green'] },
84
+ * ]
85
+ */
86
+ export type ConfigVisibleIfRule = ConfigVisibleIf | ConfigVisibleIf[];
87
+
71
88
  /** Properties common to every config field, regardless of `type`. */
72
89
  export interface BaseConfigField {
73
90
  /** Human-readable name shown next to the control in the Configs sidebar. */
@@ -79,9 +96,10 @@ export interface BaseConfigField {
79
96
  order?: number;
80
97
  /**
81
98
  * Conditional render rule. When set, the field renders only when
82
- * `Overlay.data[visibleIf.key]` matches `visibleIf.equals`.
99
+ * `Overlay.data[visibleIf.key]` matches `visibleIf.equals`. Pass an array of
100
+ * rules to require that **every** rule matches (logical AND across keys).
83
101
  */
84
- visibleIf?: ConfigVisibleIf;
102
+ visibleIf?: ConfigVisibleIfRule;
85
103
  /**
86
104
  * Hard-hide rule. When `true`, the field is never displayed in the Configs
87
105
  * sidebar, but its `value` still flows into `Overlay.data` for internal use.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumiastream/lumia-types",
3
- "version": "3.9.3",
3
+ "version": "3.9.4",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/esm/index.js",