@colixsystems/widget-sdk 0.59.0 → 0.61.0
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 +9 -3
- package/dist/contract.cjs +18 -1
- package/dist/contract.js +18 -1
- package/dist/hooks.js +21 -0
- package/dist/host.d.ts +13 -0
- package/dist/host.js +12 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +1 -0
- package/dist/index.native.js +3 -0
- package/dist/property-schema.js +53 -0
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ The data layer lives in **four separate domain-client packages**, each instantia
|
|
|
21
21
|
| **CORE** | `useWidgetStyle()` | `{ [styleField]: value }` | `ctx.props.style` — no scope. The author-set per-widget style values declared in `manifest.styleSchema`; apply each onto whatever element you choose. |
|
|
22
22
|
| **CORE** | `useUser()` | `{ id, email, displayName, roles, groupIds }` | `ctx.user` (host-built context, **camelCase** — not a wire payload; `id` null when anonymous) — no scope |
|
|
23
23
|
| **CORE** | `useNavigation()` | `{ goTo, goBack, push, replace, back, currentRoute }` | `ctx.navigation` — no scope (external URLs use the `Linking` primitive) |
|
|
24
|
+
| **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. |
|
|
24
25
|
| **CORE** | `useWidgetEvent(name)` | `(payload?) => void` | `ctx.events.emit` — no scope |
|
|
25
26
|
| **CORE** | `useChildRenderer()` | `{ renderNode(node) }` | `ctx.renderer` — no scope (prefer the `WidgetTree` component) |
|
|
26
27
|
| **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`. |
|
|
@@ -52,7 +53,11 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
52
53
|
|
|
53
54
|
## Status
|
|
54
55
|
|
|
55
|
-
`v0.
|
|
56
|
+
`v0.60.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**.
|
|
57
|
+
|
|
58
|
+
### What's new in 0.60.0
|
|
59
|
+
|
|
60
|
+
**The host fills `propertySchema` defaults onto props (sc-3228).** The platform host now applies a widget's manifest `default`s at its render boundary: for every leaf a page left unset it substitutes the declared `default`; an explicitly-bound value passes through untouched. The web Player and the native Expo export both do this against the widget's `propertySchema`, so an unset `columnRef` / field binding arrives as its declared default (e.g. `"Title"`) on BOTH hosts instead of `undefined`. **For widget authors this means: read `props.<field>` directly — the in-code `props.titleField || "Title"` fallback pattern is no longer needed and should be removed.** This is done for you by the host; there is no author API to call. (The resolver lives at the host-only subpath `@colixsystems/widget-sdk/host`, consumed by the platform hosts, not by widgets.) `CONTRACT.version` → `1.41.0`. Additive — no existing export changed signature.
|
|
56
61
|
|
|
57
62
|
### What's new in 0.59.0
|
|
58
63
|
|
|
@@ -214,7 +219,7 @@ Also: `useFileSignatures(fileIds)` is now **self-scoped** (the caller's own sign
|
|
|
214
219
|
|
|
215
220
|
**Filestore browsing + BankID file signing for widgets (REQ-FS / REQ-SIGN).** Three new hooks read a newly-injected `ctx.filestore` (the `@colixsystems/filestore-client`, now constructed by both the web and native hosts):
|
|
216
221
|
- `useFilestoreFiles({ spaceType, folderId?, q?, type? })` → `{ files, loading, error, refetch }` — browses the end-user's Filestore space. The hook resolves `owner_id` from the host context (tenant for a project space, the app user for a personal space), so the widget only picks the space.
|
|
217
|
-
- `useFilestoreFile(fileId)` → `{ file, url, loading, error, refetch }` — resolves ONE file id
|
|
222
|
+
- `useFilestoreFile(fileId)` → `{ file, url, loading, error, refetch }` — resolves ONE file id to a displayable `url` (its `presigned_url`, absolutized for web + native) via `ctx.filestore.files.get(id)`. A datastore `FILE` column holds — and reads back as — that **bare file-id string**; it is NOT hydrated into an object the way a `RELATION` (`{ id, label }`) or `USER` (`{ id, name }`) column is, so pass the value straight to the hook (no `{ id }` / `{ url }` unwrapping guard). Empty / deleted / not-found ids resolve to `url: null` so a display widget shows a fallback. Requires `files.read:*`.
|
|
218
223
|
- `useFilestoreFolders({ spaceType, parentFolderId?, q?, enabled? })` → `{ folders, loading, error, refetch }` — the folder-navigation companion to `useFilestoreFiles`; pass `enabled:false` to suspend fetching.
|
|
219
224
|
- `useFilestoreUpload({ spaceType, folderId? })` → `{ upload, uploading, error, lastUploaded }` — POSTs a multipart upload to `ctx.filestore.files.upload`. Resolves `owner_id` from the host context (like the read hooks) so the widget only picks the space + destination folder. Pair with the `<FilePicker>` primitive for the visible trigger. Requires the `files.write:*` scope.
|
|
220
225
|
- `useFileSignature(fileId)` → `{ status, qr, signerName, verdict, initiate, refresh, cancel, verify, … }` — drives a BankID signing flow for a file (the backend hashes the bytes server-side, binds the digest into the signature, and verifies the proof offline).
|
|
@@ -380,6 +385,7 @@ The "split-implementation + vetted package list" pivot.
|
|
|
380
385
|
### What's new in 0.11.0
|
|
381
386
|
|
|
382
387
|
- **`useNavigation()` is wired.** Returns the host-provided navigation surface `{ goTo, goBack, push, replace, back, currentRoute }` for internal page-to-page navigation. Missing methods degrade to no-ops on the Studio canvas preview. Additive.
|
|
388
|
+
- **`useRouteParams()` reads the nav params.** Returns `currentRoute.params` — the bag a `goTo(pageId, params)` carried to this page. The flat accessor for master→detail: navigate with `goTo(detailPageId, { recordId: row.id })`, then read `const { recordId } = useRouteParams()`. It is an OBJECT — read a param off it, never call it. Empty object when the page was opened without params. Additive (v0.61.0).
|
|
383
389
|
- **`Linking` primitive re-exported.** `Linking.openURL(url)` opens an external URL with the OS handler — web (`react-native-web`) maps to `window.open` / `location.href`; native hands off to the system. Use this for external URLs; use `useNavigation().goTo(pageId)` for internal pages.
|
|
384
390
|
|
|
385
391
|
### What's new in 0.10.0
|
|
@@ -450,7 +456,7 @@ import { defineWidget, validateManifest, useDatastoreQuery, Text, View } from "@
|
|
|
450
456
|
|
|
451
457
|
- `defineWidget({ manifest, component })` — validates the manifest and produces a widget module the host can register.
|
|
452
458
|
- `validateManifest(m)` / `validatePropertySchema(s)` / `validateProps(schema, props)` — shape validation; no third-party deps.
|
|
453
|
-
- `useDatastoreQuery`, `useDatastoreRecord`, `useDatastoreSchema`, `useDatastoreMutation`, `useDirectory`, `useUsers`, `useGroups`, `useRecordPermissions`, `useAsset`, `useWidgetEvent`, `usePayments`, `useSendNotification`, `useTheme`, `useI18n`, `useUser`, `useNavigation`, `useChildRenderer`, `useClipboard`, `useToast` — hooks that read from the host-provided `WidgetContext` (or, for `useClipboard`, the platform clipboard API directly). `useDirectory(query?)` returns `{ users, loading, error, refetch }` (each user `{ id, name, role }`) and requires the `directory.read:users` scope. `useUsers(query?)` returns `{ users, loading, error, refetch, invite, deactivate, reactivate, remove }` and requires `users.read:*` (mutations also need `users.write:*`); rejections are a `DirectoryError`. `useGroups(query?)` returns `{ groups, loading, error, refetch, create, remove, addMember, removeMember }` and requires `groups.read:*` (mutations also need `groups.write:*`). `usePayments()` returns `{ requestPayment, getPayment }` and requires the `payments.charge:appUser` scope; `requestPayment(...)` rejects with a `PaymentError`. `useSendNotification()` returns `{ send, sending, error }` and requires the `notifications.send:appUser` scope; `send({ recipient_user_id, title, body, link?, payload? })` notifies one app user in the same workspace (cross-workspace `recipient_user_id` is rejected), must be called from an event handler rather than render, and rejects with a `NotificationError`. `useUser()` returns the active end-user identity `{ id, email, displayName, roles, groupIds }` (camelCase — the host-built context object, not a wire payload; `id` is `null` for anonymous / preview). `useNavigation()` returns `{ goTo, goBack, push, replace, back, currentRoute }` for internal page navigation — for external URLs use the `Linking` primitive (`Linking.openURL(url)`). `useDatastoreRecord(tableId, recordId)` returns `{ data, loading, error, refetch }` for a single record (data is one row or null). `useDatastoreSchema(tableId)` returns `{ schema, loading, error, refetch }` where `schema` is `{ id, name, columns: [{ id, name, data_type, required, relation_type, target_table_id, is_identification }] }` (structure only, no row data; snake_case verbatim) — use it to resolve a stored `columnId` to its column type at runtime; requires the `datastore.read:<table>` scope. `useAsset(fileId)` returns `{ url, file, loading, error, refetch }` — the `url` is an absolute URL composed against the host's API base. `useChildRenderer()` returns `{ renderNode(node) }` — container widgets call it to render arbitrary child page-tree nodes (prefer the `WidgetTree` component for the common case).
|
|
459
|
+
- `useDatastoreQuery`, `useDatastoreRecord`, `useDatastoreSchema`, `useDatastoreMutation`, `useDirectory`, `useUsers`, `useGroups`, `useRecordPermissions`, `useAsset`, `useWidgetEvent`, `usePayments`, `useSendNotification`, `useTheme`, `useI18n`, `useUser`, `useNavigation`, `useRouteParams`, `useChildRenderer`, `useClipboard`, `useToast` — hooks that read from the host-provided `WidgetContext` (or, for `useClipboard`, the platform clipboard API directly). `useDirectory(query?)` returns `{ users, loading, error, refetch }` (each user `{ id, name, role }`) and requires the `directory.read:users` scope. `useUsers(query?)` returns `{ users, loading, error, refetch, invite, deactivate, reactivate, remove }` and requires `users.read:*` (mutations also need `users.write:*`); rejections are a `DirectoryError`. `useGroups(query?)` returns `{ groups, loading, error, refetch, create, remove, addMember, removeMember }` and requires `groups.read:*` (mutations also need `groups.write:*`). `usePayments()` returns `{ requestPayment, getPayment }` and requires the `payments.charge:appUser` scope; `requestPayment(...)` rejects with a `PaymentError`. `useSendNotification()` returns `{ send, sending, error }` and requires the `notifications.send:appUser` scope; `send({ recipient_user_id, title, body, link?, payload? })` notifies one app user in the same workspace (cross-workspace `recipient_user_id` is rejected), must be called from an event handler rather than render, and rejects with a `NotificationError`. `useUser()` returns the active end-user identity `{ id, email, displayName, roles, groupIds }` (camelCase — the host-built context object, not a wire payload; `id` is `null` for anonymous / preview). `useNavigation()` returns `{ goTo, goBack, push, replace, back, currentRoute }` for internal page navigation — for external URLs use the `Linking` primitive (`Linking.openURL(url)`). `useRouteParams()` returns the current route's params object (`currentRoute.params`) — the flat master→detail accessor; read a param off it (e.g. `recordId`), never call it. `useDatastoreRecord(tableId, recordId)` returns `{ data, loading, error, refetch }` for a single record (data is one row or null). `useDatastoreSchema(tableId)` returns `{ schema, loading, error, refetch }` where `schema` is `{ id, name, columns: [{ id, name, data_type, required, relation_type, target_table_id, is_identification }] }` (structure only, no row data; snake_case verbatim) — use it to resolve a stored `columnId` to its column type at runtime; requires the `datastore.read:<table>` scope. `useAsset(fileId)` returns `{ url, file, loading, error, refetch }` — the `url` is an absolute URL composed against the host's API base. `useChildRenderer()` returns `{ renderNode(node) }` — container widgets call it to render arbitrary child page-tree nodes (prefer the `WidgetTree` component for the common case).
|
|
454
460
|
- `WidgetTree({ node })` — component that renders an author-authored child node through the host's renderer; used by Tabs / Card / custom containers to host arbitrary child widgets.
|
|
455
461
|
- `Text`, `View`, `Pressable`, `Image`, `ScrollView`, `TextInput`, `FlatList`, `SectionList`, `ActivityIndicator`, `Switch`, `StyleSheet`, `Linking`, `Icon`, `DateTimePicker` — re-exported from `react-native` (the RN primitives) or implemented in the SDK (`Icon` wraps `lucide-react-native`; `DateTimePicker` wraps `@react-native-community/datetimepicker` on native and renders `<input type="date|time|datetime-local">` directly on web because the RN library has no react-native-web mapping). The web build aliases `react-native` to `react-native-web` so the RN-re-exported primitives render in the browser without any per-platform code; the exported Expo app's Metro bundler resolves the real `react-native` library. `Linking` is a static API (`Linking.openURL(url)`) — use it for external URLs, and use `useNavigation().goTo(pageId)` for internal page navigation. See https://reactnative.dev/docs/ for per-component props.
|
|
456
462
|
- `WidgetContextProvider` — React context provider that the host (Studio, Player, exported app) wraps widgets with.
|
package/dist/contract.cjs
CHANGED
|
@@ -150,6 +150,16 @@ const HOOKS = [
|
|
|
150
150
|
requiredContextSlice: ["navigation"],
|
|
151
151
|
scopes: null,
|
|
152
152
|
},
|
|
153
|
+
{
|
|
154
|
+
name: "useRouteParams",
|
|
155
|
+
signature: "useRouteParams()",
|
|
156
|
+
returnShape: {
|
|
157
|
+
"<paramKey>":
|
|
158
|
+
"a navigation param value the previous page passed via goTo(pageId, params) — usually a string id, e.g. recordId on a detail page",
|
|
159
|
+
},
|
|
160
|
+
requiredContextSlice: ["navigation"],
|
|
161
|
+
scopes: null,
|
|
162
|
+
},
|
|
153
163
|
{
|
|
154
164
|
name: "useDatastoreRecord",
|
|
155
165
|
signature: "useDatastoreRecord(tableId, recordId)",
|
|
@@ -1891,7 +1901,14 @@ const CONTRACT = deepFreeze({
|
|
|
1891
1901
|
// filestore file id (as held in a datastore FILE column) to a
|
|
1892
1902
|
// displayable `url` (its presigned_url, absolutized for web + native).
|
|
1893
1903
|
// New read hook, no existing behaviour changes — minor bump.
|
|
1894
|
-
|
|
1904
|
+
// 1.41.0: additive (sc-3228) — the host (web Player + native export) now
|
|
1905
|
+
// fills a widget's propertySchema `default`s onto props at the render
|
|
1906
|
+
// boundary, so an unset `columnRef` / field binding arrives as its declared
|
|
1907
|
+
// default instead of undefined. A widget reads `props.<field>` directly —
|
|
1908
|
+
// no in-code `|| "fallback"`. Backed by a host-only subpath export
|
|
1909
|
+
// (`@colixsystems/widget-sdk/host` -> resolveProps); the author-facing entry
|
|
1910
|
+
// is unchanged. No existing behaviour changes — minor bump.
|
|
1911
|
+
version: "1.41.0",
|
|
1895
1912
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
1896
1913
|
hooks: HOOKS,
|
|
1897
1914
|
primitives: PRIMITIVES,
|
package/dist/contract.js
CHANGED
|
@@ -150,6 +150,16 @@ const HOOKS = [
|
|
|
150
150
|
requiredContextSlice: ["navigation"],
|
|
151
151
|
scopes: null,
|
|
152
152
|
},
|
|
153
|
+
{
|
|
154
|
+
name: "useRouteParams",
|
|
155
|
+
signature: "useRouteParams()",
|
|
156
|
+
returnShape: {
|
|
157
|
+
"<paramKey>":
|
|
158
|
+
"a navigation param value the previous page passed via goTo(pageId, params) — usually a string id, e.g. recordId on a detail page",
|
|
159
|
+
},
|
|
160
|
+
requiredContextSlice: ["navigation"],
|
|
161
|
+
scopes: null,
|
|
162
|
+
},
|
|
153
163
|
{
|
|
154
164
|
name: "useDatastoreRecord",
|
|
155
165
|
signature: "useDatastoreRecord(tableId, recordId)",
|
|
@@ -1891,7 +1901,14 @@ const CONTRACT = deepFreeze({
|
|
|
1891
1901
|
// filestore file id (as held in a datastore FILE column) to a
|
|
1892
1902
|
// displayable `url` (its presigned_url, absolutized for web + native).
|
|
1893
1903
|
// New read hook, no existing behaviour changes — minor bump.
|
|
1894
|
-
|
|
1904
|
+
// 1.41.0: additive (sc-3228) — the host (web Player + native export) now
|
|
1905
|
+
// fills a widget's propertySchema `default`s onto props at the render
|
|
1906
|
+
// boundary, so an unset `columnRef` / field binding arrives as its declared
|
|
1907
|
+
// default instead of undefined. A widget reads `props.<field>` directly —
|
|
1908
|
+
// no in-code `|| "fallback"`. Backed by a host-only subpath export
|
|
1909
|
+
// (`@colixsystems/widget-sdk/host` -> resolveProps); the author-facing entry
|
|
1910
|
+
// is unchanged. No existing behaviour changes — minor bump.
|
|
1911
|
+
version: "1.41.0",
|
|
1895
1912
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
1896
1913
|
hooks: HOOKS,
|
|
1897
1914
|
primitives: PRIMITIVES,
|
package/dist/hooks.js
CHANGED
|
@@ -249,6 +249,27 @@ export function useNavigation() {
|
|
|
249
249
|
return ctx.navigation;
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
+
const EMPTY_PARAMS = Object.freeze({});
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Returns the current route's navigation params — the bag a `goTo(pageId, params)`
|
|
256
|
+
* carried to this page (a query string on web, react-navigation route params on
|
|
257
|
+
* native). This is the flat accessor for the master→detail pattern: a list widget
|
|
258
|
+
* navigates with `goTo(detailPageId, { recordId: row.id })`, and the detail widget
|
|
259
|
+
* reads `const { recordId } = useRouteParams()`. Returns an empty object on a page
|
|
260
|
+
* opened without params (the Studio canvas, a direct deep link), so reads are
|
|
261
|
+
* always safe. Equivalent to `useNavigation().currentRoute.params`, but without the
|
|
262
|
+
* deep chain — read a param off it, never call it.
|
|
263
|
+
*/
|
|
264
|
+
export function useRouteParams() {
|
|
265
|
+
const ctx = useWidgetContextOrThrow("useRouteParams");
|
|
266
|
+
const nav = ctx.navigation || {};
|
|
267
|
+
const route = nav.currentRoute || {};
|
|
268
|
+
// Stable reference on the empty path so `useEffect([params])` doesn't re-fire
|
|
269
|
+
// every render on a paramless page.
|
|
270
|
+
return route.params || EMPTY_PARAMS;
|
|
271
|
+
}
|
|
272
|
+
|
|
252
273
|
/**
|
|
253
274
|
* Returns { t, locale }. `t(key, fallback)` resolves `{{t:key}}` against
|
|
254
275
|
* the host's translation table and falls back to `fallback ?? key` when
|
package/dist/host.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Host-integration surface — not the author-facing widget API. See host.js.
|
|
2
|
+
import type { WidgetPropertySchema } from "./index";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Host render-boundary helper: fills a widget's propertySchema `default`s onto
|
|
6
|
+
* props for unset (undefined/null) leaves; explicitly-bound values pass
|
|
7
|
+
* through. Never validates or coerces; always returns the merged object.
|
|
8
|
+
* Applied by the platform hosts, never by widget authors.
|
|
9
|
+
*/
|
|
10
|
+
export function resolveProps<T = Record<string, unknown>>(
|
|
11
|
+
schema: WidgetPropertySchema,
|
|
12
|
+
props: unknown,
|
|
13
|
+
): T;
|
package/dist/host.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Host-integration surface — NOT part of the author-facing widget API.
|
|
2
|
+
// These exports are consumed only by the platform hosts that render widgets
|
|
3
|
+
// (the web Player / Studio Canvas and the native Expo export), never by a
|
|
4
|
+
// widget author. Authors receive already-correct props and never call these.
|
|
5
|
+
//
|
|
6
|
+
// `resolveProps` lives here (re-exported from property-schema.js, the single
|
|
7
|
+
// source of schema semantics) so the host can fill a widget's declared
|
|
8
|
+
// propertySchema `default`s onto props at its render boundary. It is
|
|
9
|
+
// deliberately kept off the main entry (`@colixsystems/widget-sdk`) so it does
|
|
10
|
+
// not appear in the author import surface or the Developer guide.
|
|
11
|
+
|
|
12
|
+
export { resolveProps } from "./property-schema.js";
|
package/dist/index.d.ts
CHANGED
|
@@ -925,6 +925,15 @@ export function useNavigation(): {
|
|
|
925
925
|
currentRoute: { pageId: string; params: Record<string, unknown> };
|
|
926
926
|
};
|
|
927
927
|
|
|
928
|
+
/**
|
|
929
|
+
* Returns the current route's navigation params — the bag a `goTo(pageId, params)`
|
|
930
|
+
* carried to this page. The flat accessor for master→detail: navigate with
|
|
931
|
+
* `goTo(detailPageId, { recordId: row.id })`, then read
|
|
932
|
+
* `const { recordId } = useRouteParams()`. Empty object when the page was opened
|
|
933
|
+
* without params. Equivalent to `useNavigation().currentRoute.params`.
|
|
934
|
+
*/
|
|
935
|
+
export function useRouteParams(): Record<string, unknown>;
|
|
936
|
+
|
|
928
937
|
/**
|
|
929
938
|
* Static API for external URLs. `openURL(url)` opens a URL with the OS
|
|
930
939
|
* handler (web: react-native-web maps to `window.open` / `location.href`;
|
package/dist/index.js
CHANGED
package/dist/index.native.js
CHANGED
|
@@ -24,6 +24,8 @@ export {
|
|
|
24
24
|
useFilestoreFolders,
|
|
25
25
|
useFileSignature,
|
|
26
26
|
useFileSignatures,
|
|
27
|
+
useFileRoster,
|
|
28
|
+
useFolderPermissions,
|
|
27
29
|
useDatastoreMutation,
|
|
28
30
|
useDirectory,
|
|
29
31
|
useUsers,
|
|
@@ -40,6 +42,7 @@ export {
|
|
|
40
42
|
useUser,
|
|
41
43
|
useFill,
|
|
42
44
|
useNavigation,
|
|
45
|
+
useRouteParams,
|
|
43
46
|
useChildRenderer,
|
|
44
47
|
useRefresh,
|
|
45
48
|
useGeolocation,
|
package/dist/property-schema.js
CHANGED
|
@@ -243,3 +243,56 @@ export function validateProps(schema, props) {
|
|
|
243
243
|
}
|
|
244
244
|
return errors.length === 0 ? { ok: true, value: out } : { ok: false, errors };
|
|
245
245
|
}
|
|
246
|
+
|
|
247
|
+
// Defaults are static config; clone object/array ones so a widget mutating
|
|
248
|
+
// its props can never corrupt the shared manifest default.
|
|
249
|
+
function cloneDefault(value) {
|
|
250
|
+
if (value === null || typeof value !== "object") return value;
|
|
251
|
+
return JSON.parse(JSON.stringify(value));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function resolveLeaf(def, value) {
|
|
255
|
+
if (!isPlainObject(def)) return value;
|
|
256
|
+
if (value === undefined || value === null) {
|
|
257
|
+
return def.default !== undefined ? cloneDefault(def.default) : value;
|
|
258
|
+
}
|
|
259
|
+
if (
|
|
260
|
+
def.type === "object" &&
|
|
261
|
+
isPlainObject(def.properties) &&
|
|
262
|
+
isPlainObject(value)
|
|
263
|
+
) {
|
|
264
|
+
const out = { ...value };
|
|
265
|
+
for (const [k, child] of Object.entries(def.properties)) {
|
|
266
|
+
out[k] = resolveLeaf(child, value[k]);
|
|
267
|
+
}
|
|
268
|
+
return out;
|
|
269
|
+
}
|
|
270
|
+
// Fill per-element defaults for an author-supplied array<object> (e.g. a
|
|
271
|
+
// `columns` list whose item objects carry field-level defaults).
|
|
272
|
+
if (def.type === "array" && isPlainObject(def.items) && Array.isArray(value)) {
|
|
273
|
+
return value.map((item) => resolveLeaf(def.items, item));
|
|
274
|
+
}
|
|
275
|
+
return value;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Fills in a widget's `propertySchema` defaults on `props` for the host render
|
|
280
|
+
* boundary: any leaf the author left unset (undefined/null) takes its declared
|
|
281
|
+
* `default`; an explicitly-set value passes through untouched. Unlike
|
|
282
|
+
* {@link validateProps} it never validates or coerces and always returns the
|
|
283
|
+
* merged object — a partially-configured binding stays valid while the author
|
|
284
|
+
* is still picking. Applied once by the host (web Player + native export) so a
|
|
285
|
+
* widget reads `props.<field>` directly without in-code fallbacks.
|
|
286
|
+
* @param {Record<string, any>} schema
|
|
287
|
+
* @param {unknown} props
|
|
288
|
+
* @returns {Record<string, unknown>}
|
|
289
|
+
*/
|
|
290
|
+
export function resolveProps(schema, props) {
|
|
291
|
+
const input = isPlainObject(props) ? props : {};
|
|
292
|
+
if (!isPlainObject(schema)) return input;
|
|
293
|
+
const out = { ...input };
|
|
294
|
+
for (const [k, def] of Object.entries(schema)) {
|
|
295
|
+
out[k] = resolveLeaf(def, input[k]);
|
|
296
|
+
}
|
|
297
|
+
return out;
|
|
298
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.61.0",
|
|
4
4
|
"description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,6 +22,12 @@
|
|
|
22
22
|
"import": "./dist/dev-shims.js",
|
|
23
23
|
"default": "./dist/dev-shims.js"
|
|
24
24
|
},
|
|
25
|
+
"./host": {
|
|
26
|
+
"types": "./dist/host.d.ts",
|
|
27
|
+
"react-native": "./dist/host.js",
|
|
28
|
+
"import": "./dist/host.js",
|
|
29
|
+
"default": "./dist/host.js"
|
|
30
|
+
},
|
|
25
31
|
"./contract": {
|
|
26
32
|
"types": "./dist/index.d.ts",
|
|
27
33
|
"require": "./dist/contract.cjs",
|
|
@@ -42,7 +48,7 @@
|
|
|
42
48
|
],
|
|
43
49
|
"scripts": {
|
|
44
50
|
"build": "node scripts/build.js",
|
|
45
|
-
"test": "node --test src/__tests__/contract.test.js src/__tests__/hooks-users.test.js src/__tests__/hooks-groups.test.js src/__tests__/hooks-schema.test.js src/__tests__/hooks-assets-by-tag.test.js src/__tests__/hooks-filestore-upload.test.js src/__tests__/hooks-mutation.test.js src/__tests__/hooks-record-permissions.test.js src/__tests__/hooks-geolocation.test.js src/__tests__/hooks-subscription.test.js src/__tests__/linter-users-scope.test.js src/__tests__/linter-comments.test.js src/__tests__/linter-platform.test.js src/__tests__/linter-react-import.test.js src/__tests__/lucide-icon-names.test.js src/__tests__/lucideIconName.test.js src/__tests__/manifest-actions.test.js src/__tests__/widget-translations.test.js src/__tests__/devserver.test.js src/__tests__/host-externals.test.js src/__tests__/datetimepicker.test.js"
|
|
51
|
+
"test": "node --test src/__tests__/contract.test.js src/__tests__/hooks-users.test.js src/__tests__/hooks-groups.test.js src/__tests__/hooks-schema.test.js src/__tests__/hooks-assets-by-tag.test.js src/__tests__/hooks-filestore-upload.test.js src/__tests__/hooks-mutation.test.js src/__tests__/hooks-record-permissions.test.js src/__tests__/hooks-geolocation.test.js src/__tests__/hooks-subscription.test.js src/__tests__/linter-users-scope.test.js src/__tests__/linter-comments.test.js src/__tests__/linter-platform.test.js src/__tests__/linter-react-import.test.js src/__tests__/lucide-icon-names.test.js src/__tests__/lucideIconName.test.js src/__tests__/manifest-actions.test.js src/__tests__/widget-translations.test.js src/__tests__/devserver.test.js src/__tests__/host-externals.test.js src/__tests__/datetimepicker.test.js src/__tests__/property-schema-resolve.test.js"
|
|
46
52
|
},
|
|
47
53
|
"engines": {
|
|
48
54
|
"node": ">=18"
|