@colixsystems/widget-sdk 0.172.0 → 0.173.1

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/README.md CHANGED
@@ -25,7 +25,7 @@ The data layer lives in **four separate domain-client packages**, each instantia
25
25
  | **CORE** | `useRouteParams()` | `{ [paramKey]: value }` | `ctx.navigation.currentRoute.params` — no scope. The nav params the previous page passed via `goTo(pageId, params)`; the flat accessor for master→detail (read `recordId` on a detail page). Empty object when none. |
26
26
  | **CORE** | `usePageContext()` | `{ params, records }` | `ctx.pageContext` — no scope. The page's DECLARED parameters, resolved once by the host: `params` are coerced to their declared types, `records` holds the row already fetched for each `record` param (read it instead of fetching again). Both empty when the page declares none. |
27
27
  | **CORE** | `useWidgetRoute(initial)` | `[state, setState]` | `ctx.widgetRoute` — no scope. Where YOUR WIDGET is, persisted by the host so it survives a reload and travels in a shared link (the `w_<instanceId>` query key on web, the screen's route params natively): the folder a browser has opened, a wizard step, a selected tab, a list's sort and search. `useState` semantics over an object — writes MERGE, `null` clears a key back to its `initial`, and `initial` is read once. Values are scalars or flat arrays of scalars, size- and length-capped; anything else is not stored. NOT history (Back still leaves the page, on both platforms). Degrades to component state on the Studio canvas. |
28
- | **CORE** | `useWidgetEvent(name)` | `(payload?) => void` | `ctx.events.emit` — no scope. The hook IS the emitter: `const emitSlot = useWidgetEvent("slotChosen")`, then `emitSlot(payload)`. Never destructure the result — there is no `emit` member. |
28
+ | **CORE** | `useWidgetEvent(name)` | `(payload?) => void` | `ctx.events.emit` — no scope. The hook IS the emitter: `const emitSlot = useWidgetEvent("slotChosen")`, then `emitSlot(payload)`. Never destructure the result — there is no `emit` member. A sibling's input reads the payload, and a page action the author binds to the event (Run action, Navigate) runs with it — fired from one control, only that control runs it. |
29
29
  | **CORE** | `useWidgetInput(inputName)` | the published payload, or `undefined` | `ctx.inputs` — no scope. Reads a value ANOTHER widget on the same page published with `useWidgetEvent`. Declare the input in `manifest.inputs`; the page author wires it to one sibling's declared event. The channel retains the last payload, so a widget that mounts later still reads it. `undefined` while unwired or before the first publish — always render a sensible default. Page-scoped and ephemeral: use `useRouteParams()` for state that must survive navigation, the datastore for state that must persist. |
30
30
  | **CORE** | `useChildRenderer()` | `{ renderNode(node) }` | `ctx.renderer` — no scope (prefer the `WidgetTree` component) |
31
31
  | **CORE** | `useFill()` | `boolean` | `ctx.fill` — no scope. `true` when the host sized this widget to fill its page-grid tile's reserved height (containers + media fill by default; the author can override per tile). Media-style widgets switch to a `flex: 1` / `height: "100%"` layout; others ignore it. Defaults `false`. |
@@ -73,7 +73,23 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
73
73
 
74
74
  ## Status
75
75
 
76
- `v0.172.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
76
+ `v0.173.1` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
77
+
78
+ ### What's new in 0.173.1 (contract 1.134.1)
79
+
80
+ **An event your widget fires can run a page action, from the one control that fires it.** The author (or Mason) can bind Run action or Navigate to any event your manifest declares, and it runs when `useWidgetEvent` fires it. Fire the event from one control and only that control runs the action:
81
+
82
+ ```jsx
83
+ // manifest: events: [{ name: "onBookPressed", payloadSchema: { classId: { type: "string" } } }]
84
+ const emitBook = useWidgetEvent("onBookPressed");
85
+ <Button title="Book" onPress={() => emitBook({ classId })} />
86
+ ```
87
+
88
+ Never declare your own event as `onPress`: an `onPress` binding makes the whole widget one tap target, so a tap anywhere on it runs the action. Documentation only — both hosts already dispatched a named event's binding.
89
+
90
+ ### What's new in 0.173.0 (contract 1.134.0)
91
+
92
+ **A `datastoreTemplate` RELATION may name any table in the template.** The installer now creates every table before any column, so `targetSuffix` may point at a table declared later in `tables`, and two tables may relate to each other both ways. A suffix no table in the template has is still rejected at publish. No change is needed to an existing template.
77
93
 
78
94
  ### What's new in 0.172.0 (contract 1.133.0)
79
95
 
package/dist/contract.cjs CHANGED
@@ -1558,6 +1558,12 @@ const HOOKS = [
1558
1558
  {
1559
1559
  name: "useWidgetEvent",
1560
1560
  signature: "useWidgetEvent(eventName)",
1561
+ description:
1562
+ "Fires one of the events the manifest declares. The page wires it two " +
1563
+ "ways: a sibling widget's input reads the payload, and an author-bound " +
1564
+ "page action (Run action, Navigate) runs with it. Fire a named event " +
1565
+ "from ONE control to have only that control run the action; an onPress " +
1566
+ "binding instead makes the whole widget the tap target.",
1561
1567
  // sc-4753 — the hook IS the emitter, so its return is declared as a bare
1562
1568
  // callable. Naming an `emit` member here made every derived surface tell
1563
1569
  // authors to destructure a function, which yields undefined at run time.
@@ -2633,7 +2639,10 @@ const MANIFEST_SCHEMA = {
2633
2639
  "{ type, label? } — the SAME shape as propertySchema, NOT JSON Schema — " +
2634
2640
  "where type is one of string, number, boolean, object, array. The payload reaches " +
2635
2641
  "any sibling widget on the page whose inputs entry the page author wired " +
2636
- "to this event.",
2642
+ "to this event. An event can also run a page action the author binds to " +
2643
+ "it (Run action, Navigate): fired from one control with useWidgetEvent, " +
2644
+ "only that control runs the action. Never declare it as onPress, which " +
2645
+ "the host binds as a tap target over the whole widget.",
2637
2646
  default: [],
2638
2647
  },
2639
2648
  inputs: {
@@ -2652,7 +2661,7 @@ const MANIFEST_SCHEMA = {
2652
2661
  type: "object",
2653
2662
  required: false,
2654
2663
  description:
2655
- "Optional. Tables the widget needs, seeded into the workspace at install time. Authors wire them into the widget's `tableRef` properties via the Properties Panel — the SDK does not auto-bind. Limits: 8 tables, 24 columns per table. RELATION columns address siblings by `targetSuffix` (must be declared earlier in the array). Tables persist across uninstalls. A `tableRef` property may also carry `sharedTable: true`, declaring that the table is the APP'S OWN — the author picks an existing table and the widget seeds nothing for it. That is the correct shape for a widget that reads data it does not own (a chart, a metric, a manager view over another widget's table). `sharedTable` exempts the property from needing a seeded table at publish (`manifest.requiredTableRefsHaveTemplate`) WITHOUT making it optional: the author must still bind one, and the host still refuses to place the widget unbound. Use it instead of dropping `required` — a widget that silently renders an empty tile is the defect it looks like a fix for. sc-7530: a table may also declare `roleGrants` — group-scoped access named SYMBOLICALLY, because a group id belongs to the workspace that installs the widget and could never travel in the manifest. Declare the audiences once at template level as `roles: [{ key, label, description? }]` (key is lowercase kebab, max 8 roles), then per table `roleGrants: [{ role, canRead?, canWrite?, canDelete? }]`. The installing workspace binds each role to one of its own user groups (or has one created) during the install consent step, so a permission model where an admin group does everything and signed-in users only read survives the trip between workspaces. Use `publicGrant` for the two id-less audiences (everyone / any signed-in user) and `roleGrants` for anything that names a group. A role nobody binds simply writes no grant — it never fails the install. sc-7557: a column may declare `encrypted: true` — its values are stored AES-256-GCM encrypted at rest under a per-workspace subkey. The widget still reads and writes PLAINTEXT (the platform encrypts on write, decrypts for end users); studio users see `🔒` instead, and the column cannot be searched, filtered or sorted on. Sample `rows` you seed for it are encrypted the same way. Not valid on `RELATION` — its value is a foreign key the backend must resolve — and publishing one is rejected.",
2664
+ "Optional. Tables the widget needs, seeded into the workspace at install time. Authors wire them into the widget's `tableRef` properties via the Properties Panel — the SDK does not auto-bind. Limits: 8 tables, 24 columns per table. RELATION columns address siblings by `targetSuffix` (any table in the same array, in any order; tables may relate to each other both ways). Tables persist across uninstalls. A `tableRef` property may also carry `sharedTable: true`, declaring that the table is the APP'S OWN — the author picks an existing table and the widget seeds nothing for it. That is the correct shape for a widget that reads data it does not own (a chart, a metric, a manager view over another widget's table). `sharedTable` exempts the property from needing a seeded table at publish (`manifest.requiredTableRefsHaveTemplate`) WITHOUT making it optional: the author must still bind one, and the host still refuses to place the widget unbound. Use it instead of dropping `required` — a widget that silently renders an empty tile is the defect it looks like a fix for. sc-7530: a table may also declare `roleGrants` — group-scoped access named SYMBOLICALLY, because a group id belongs to the workspace that installs the widget and could never travel in the manifest. Declare the audiences once at template level as `roles: [{ key, label, description? }]` (key is lowercase kebab, max 8 roles), then per table `roleGrants: [{ role, canRead?, canWrite?, canDelete? }]`. The installing workspace binds each role to one of its own user groups (or has one created) during the install consent step, so a permission model where an admin group does everything and signed-in users only read survives the trip between workspaces. Use `publicGrant` for the two id-less audiences (everyone / any signed-in user) and `roleGrants` for anything that names a group. A role nobody binds simply writes no grant — it never fails the install. sc-7557: a column may declare `encrypted: true` — its values are stored AES-256-GCM encrypted at rest under a per-workspace subkey. The widget still reads and writes PLAINTEXT (the platform encrypts on write, decrypts for end users); studio users see `🔒` instead, and the column cannot be searched, filtered or sorted on. Sample `rows` you seed for it are encrypted the same way. Not valid on `RELATION` — its value is a foreign key the backend must resolve — and publishing one is rejected.",
2656
2665
  },
2657
2666
  translations: {
2658
2667
  type: "object",
@@ -4661,7 +4670,8 @@ const CONTRACT = deepFreeze({
4661
4670
  // 1.133.0: additive (sc-9002) — `ListHeader` (column labels on `ListRow`'s frame);
4662
4671
  // a `ListRow`'s lone `View` child spans the row; a selected row's bar
4663
4672
  // no longer shifts its content. Other rows render as before.
4664
- version: "1.133.0",
4673
+ // 1.134.1: docs (sc-9122) — an emitted event can run a bound action.
4674
+ version: "1.134.1",
4665
4675
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
4666
4676
  hooks: HOOKS,
4667
4677
  primitives: PRIMITIVES,
package/dist/contract.js CHANGED
@@ -1558,6 +1558,12 @@ const HOOKS = [
1558
1558
  {
1559
1559
  name: "useWidgetEvent",
1560
1560
  signature: "useWidgetEvent(eventName)",
1561
+ description:
1562
+ "Fires one of the events the manifest declares. The page wires it two " +
1563
+ "ways: a sibling widget's input reads the payload, and an author-bound " +
1564
+ "page action (Run action, Navigate) runs with it. Fire a named event " +
1565
+ "from ONE control to have only that control run the action; an onPress " +
1566
+ "binding instead makes the whole widget the tap target.",
1561
1567
  // sc-4753 — the hook IS the emitter, so its return is declared as a bare
1562
1568
  // callable. Naming an `emit` member here made every derived surface tell
1563
1569
  // authors to destructure a function, which yields undefined at run time.
@@ -2629,7 +2635,10 @@ const MANIFEST_SCHEMA = {
2629
2635
  "{ type, label? } — the SAME shape as propertySchema, NOT JSON Schema — " +
2630
2636
  "where type is one of string, number, boolean, object, array. The payload reaches " +
2631
2637
  "any sibling widget on the page whose inputs entry the page author wired " +
2632
- "to this event.",
2638
+ "to this event. An event can also run a page action the author binds to " +
2639
+ "it (Run action, Navigate): fired from one control with useWidgetEvent, " +
2640
+ "only that control runs the action. Never declare it as onPress, which " +
2641
+ "the host binds as a tap target over the whole widget.",
2633
2642
  default: [],
2634
2643
  },
2635
2644
  inputs: {
@@ -2648,7 +2657,7 @@ const MANIFEST_SCHEMA = {
2648
2657
  type: "object",
2649
2658
  required: false,
2650
2659
  description:
2651
- "Optional. Tables the widget needs, seeded into the workspace at install time. Authors wire them into the widget's `tableRef` properties via the Properties Panel — the SDK does not auto-bind. Limits: 8 tables, 24 columns per table. RELATION columns address siblings by `targetSuffix` (must be declared earlier in the array). Tables persist across uninstalls. A `tableRef` property may also carry `sharedTable: true`, declaring that the table is the APP'S OWN — the author picks an existing table and the widget seeds nothing for it. That is the correct shape for a widget that reads data it does not own (a chart, a metric, a manager view over another widget's table). `sharedTable` exempts the property from needing a seeded table at publish (`manifest.requiredTableRefsHaveTemplate`) WITHOUT making it optional: the author must still bind one, and the host still refuses to place the widget unbound. Use it instead of dropping `required` — a widget that silently renders an empty tile is the defect it looks like a fix for. sc-7530: a table may also declare `roleGrants` — group-scoped access named SYMBOLICALLY, because a group id belongs to the workspace that installs the widget and could never travel in the manifest. Declare the audiences once at template level as `roles: [{ key, label, description? }]` (key is lowercase kebab, max 8 roles), then per table `roleGrants: [{ role, canRead?, canWrite?, canDelete? }]`. The installing workspace binds each role to one of its own user groups (or has one created) during the install consent step, so a permission model where an admin group does everything and signed-in users only read survives the trip between workspaces. Use `publicGrant` for the two id-less audiences (everyone / any signed-in user) and `roleGrants` for anything that names a group. A role nobody binds simply writes no grant — it never fails the install. sc-7557: a column may declare `encrypted: true` — its values are stored AES-256-GCM encrypted at rest under a per-workspace subkey. The widget still reads and writes PLAINTEXT (the platform encrypts on write, decrypts for end users); studio users see `🔒` instead, and the column cannot be searched, filtered or sorted on. Sample `rows` you seed for it are encrypted the same way. Not valid on `RELATION` — its value is a foreign key the backend must resolve — and publishing one is rejected.",
2660
+ "Optional. Tables the widget needs, seeded into the workspace at install time. Authors wire them into the widget's `tableRef` properties via the Properties Panel — the SDK does not auto-bind. Limits: 8 tables, 24 columns per table. RELATION columns address siblings by `targetSuffix` (any table in the same array, in any order; tables may relate to each other both ways). Tables persist across uninstalls. A `tableRef` property may also carry `sharedTable: true`, declaring that the table is the APP'S OWN — the author picks an existing table and the widget seeds nothing for it. That is the correct shape for a widget that reads data it does not own (a chart, a metric, a manager view over another widget's table). `sharedTable` exempts the property from needing a seeded table at publish (`manifest.requiredTableRefsHaveTemplate`) WITHOUT making it optional: the author must still bind one, and the host still refuses to place the widget unbound. Use it instead of dropping `required` — a widget that silently renders an empty tile is the defect it looks like a fix for. sc-7530: a table may also declare `roleGrants` — group-scoped access named SYMBOLICALLY, because a group id belongs to the workspace that installs the widget and could never travel in the manifest. Declare the audiences once at template level as `roles: [{ key, label, description? }]` (key is lowercase kebab, max 8 roles), then per table `roleGrants: [{ role, canRead?, canWrite?, canDelete? }]`. The installing workspace binds each role to one of its own user groups (or has one created) during the install consent step, so a permission model where an admin group does everything and signed-in users only read survives the trip between workspaces. Use `publicGrant` for the two id-less audiences (everyone / any signed-in user) and `roleGrants` for anything that names a group. A role nobody binds simply writes no grant — it never fails the install. sc-7557: a column may declare `encrypted: true` — its values are stored AES-256-GCM encrypted at rest under a per-workspace subkey. The widget still reads and writes PLAINTEXT (the platform encrypts on write, decrypts for end users); studio users see `🔒` instead, and the column cannot be searched, filtered or sorted on. Sample `rows` you seed for it are encrypted the same way. Not valid on `RELATION` — its value is a foreign key the backend must resolve — and publishing one is rejected.",
2652
2661
  },
2653
2662
  translations: {
2654
2663
  type: "object",
@@ -4657,7 +4666,8 @@ const CONTRACT = deepFreeze({
4657
4666
  // 1.133.0: additive (sc-9002) — `ListHeader` (column labels on `ListRow`'s frame);
4658
4667
  // a `ListRow`'s lone `View` child spans the row; a selected row's bar
4659
4668
  // no longer shifts its content. Other rows render as before.
4660
- version: "1.133.0",
4669
+ // 1.134.1: docs (sc-9122) — an emitted event can run a bound action.
4670
+ version: "1.134.1",
4661
4671
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
4662
4672
  hooks: HOOKS,
4663
4673
  primitives: PRIMITIVES,
package/dist/index.d.ts CHANGED
@@ -160,9 +160,10 @@ export interface WidgetInputDescriptor {
160
160
  * - At most 24 columns per table. Same reason.
161
161
  *
162
162
  * RELATION columns reference siblings within the same template via
163
- * `targetSuffix` — that suffix MUST belong to a table declared earlier
164
- * in the array. There is intentionally no way to reference a table
165
- * outside the widget's own template.
163
+ * `targetSuffix` — that suffix MUST belong to a table in the same array,
164
+ * declared anywhere in it; tables may relate to each other both ways.
165
+ * There is intentionally no way to reference a table outside the
166
+ * widget's own template.
166
167
  */
167
168
  export interface WidgetDatastoreTemplateColumn {
168
169
  name: string;
@@ -896,6 +897,13 @@ export interface DirectoryResult {
896
897
  */
897
898
  export function useDirectory(query?: DirectoryQuery): DirectoryResult;
898
899
 
900
+ /**
901
+ * The emitter for an event the manifest declares. A sibling's wired input
902
+ * reads the payload, and a page action the author binds to the event (Run
903
+ * action, Navigate) runs with it — fired from one control, only that control
904
+ * runs it. Never declare an event named `onPress`: that binding makes the
905
+ * whole widget one tap target.
906
+ */
899
907
  export function useWidgetEvent(name: string): (payload?: unknown) => void;
900
908
 
901
909
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colixsystems/widget-sdk",
3
- "version": "0.172.0",
3
+ "version": "0.173.1",
4
4
  "description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
5
5
  "homepage": "https://github.com/Colix-AB/AppStudio",
6
6
  "type": "module",