@colixsystems/widget-sdk 0.53.0 → 0.54.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 CHANGED
@@ -19,7 +19,7 @@ The data layer lives in **four separate domain-client packages**, each instantia
19
19
  | ----- | ---------------- | ------- | ------------- |
20
20
  | **CORE** | `useTheme()` | `{ colors, spacing, radii, typography }` | `ctx.workspace.theme` — no scope |
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
- | **CORE** | `useUser()` | `{ id, email, display_name, roles, group_ids }` | `ctx.user` (snake_case verbatim; `id` null when anonymous) — no scope |
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
24
  | **CORE** | `useWidgetEvent(name)` | `(payload?) => void` | `ctx.events.emit` — no scope |
25
25
  | **CORE** | `useChildRenderer()` | `{ renderNode(node) }` | `ctx.renderer` — no scope (prefer the `WidgetTree` component) |
@@ -40,6 +40,7 @@ The data layer lives in **four separate domain-client packages**, each instantia
40
40
  | **DIRECTORY** | `useUsers(query?)` | `{ users, loading, error, refetch, invite, deactivate, reactivate, remove }` | `directory.users.*` — `users.read:*` (edits also `users.write:*`; `remove()` also `users.delete:*`) |
41
41
  | **DIRECTORY** | `useGroups(query?)` | `{ groups, loading, error, refetch, create, remove, addMember, removeMember }` | `directory.groups.*` — `groups.read:*` (mutations also `groups.write:*`) |
42
42
  | **DIRECTORY** | `useBankIdLink()` | `{ linked, available, status, qr, message, startLink, refresh, cancel, unlink, refetchStatus, … }` | `directory.bankid.*` — no scope (JWT-gated self-service) |
43
+ | **FILESTORE** (`ctx.filestore`) | `usePdfExport({ spaceType, folderId? })` | `{ exportToPdf, exporting, error, lastExported }` | `ctx.filestore.files.exportPdf` — `files.write:*`. `exportToPdf(html, { fileName?, folderId? })` renders the HTML to a PDF server-side and saves it as a file (`application/pdf`); same server-side renderer on web + native. |
43
44
  | **PAYMENTS** (`ctx.payments`) | `usePayments()` | `{ requestPayment, getPayment }` | `ctx.payments.*` — `payments.charge:appUser` |
44
45
  | **NOTIFICATIONS** (`ctx.notifications`) | `useSendNotification()` | `{ send, sending, error }` | `ctx.notifications.send` — `notifications.send:appUser`. `send({ recipient_user_id, title, body, link?, payload? })` notifies one app user in the same workspace; call from an event handler (never render); rejects with `NotificationError`. |
45
46
 
@@ -51,7 +52,11 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
51
52
 
52
53
  ## Status
53
54
 
54
- `v0.53.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**.
55
+ `v0.54.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**.
56
+
57
+ ### What's new in 0.54.0
58
+
59
+ **Generate & save PDFs from a widget (sc-2314).** New `usePdfExport({ spaceType, folderId? })` hook. `exportToPdf(html, { fileName?, folderId? })` renders the HTML to a PDF **server-side** (the platform's headless-Chromium pipeline) and saves it into the end-user's Filestore via `ctx.filestore.files.exportPdf`, resolving to the created file row (`application/pdf`). It reuses the filestore owner_id resolution + per-folder write gate and the existing `files.write:*` scope. Because the rendering is server-side, the capability behaves identically on the web Player and the native Expo export — no browser-only PDF library is added to the vetted set. Pairs with `@colixsystems/filestore-client@0.6.0`'s new `files.exportPdf(...)`. `CONTRACT.version` → `1.38.0`. Additive — no existing hook, primitive, manifest field, or token changed shape.
55
60
 
56
61
  ### What's new in 0.53.0
57
62
 
@@ -278,7 +283,7 @@ Also: `useFileSignatures(fileIds)` is now **self-scoped** (the caller's own sign
278
283
 
279
284
  - **The SDK no longer owns any data facade.** The bespoke per-hook facades that used to live on `WidgetContext` (`ctx.datastore` as an opaque host object, `ctx.directory.listUsers`, `ctx.users`, `ctx.groups`, `ctx.recordPermissions`, `ctx.assets.get`) are replaced by four host-instantiated, host-injected domain clients: `ctx.datastore` (`@colixsystems/datastore-client`), `ctx.directory` (`@colixsystems/directory-client`), `ctx.assets` (`@colixsystems/assets-client`, flattened), `ctx.payments` (`@colixsystems/payments-client`). The SDK imports none of them and ships no HTTP.
280
285
  - **`ctx.recordPermissions`, `ctx.users`, `ctx.groups` are removed.** Per-record permission management moved under `ctx.datastore.records(tableId).permissions(recordId)`; user / group administration moved under `ctx.directory.users` / `ctx.directory.groups`. The hooks (`useRecordPermissions`, `useUsers`, `useGroups`) keep the same names and signatures — only the client slice they read changed.
281
- - **snake_case end to end, no client-side transform.** Clients send and return snake_case verbatim (`group_ids`, `can_read`, `is_active`, `amount_cents`, `data_type`, `created_at`, …). The SDK passes bodies straight through and unwraps the `{ data, meta }` list envelope without renaming a single field. Hook return rows and `useUser()` fields are therefore snake_case (`display_name`, `group_ids`, `is_active`, `member_count`, and `useRecordPermissions` rows carry `user_id` / `group_id` / `can_read` / `can_write` / `can_delete` / `can_grant`).
286
+ - **snake_case end to end, no client-side transform.** Clients send and return snake_case verbatim (`group_ids`, `can_read`, `is_active`, `amount_cents`, `data_type`, `created_at`, …). The SDK passes bodies straight through and unwraps the `{ data, meta }` list envelope without renaming a single field. Wire-payload hook return rows are therefore snake_case (`is_active`, `member_count`, and `useRecordPermissions` rows carry `user_id` / `group_id` / `can_read` / `can_write` / `can_delete` / `can_grant`). The one exception is `useUser()`: it reads the host-built `ctx.user` context object, not a wire payload, so its fields are **camelCase** (`displayName`, `groupIds`).
282
287
  - **Companion package versions:** `datastore-client 0.5.0`, `assets-client 0.4.0`, `directory-client 0.1.0`, `payments-client 0.1.0`.
283
288
  - **`CONTRACT.version` → `1.9.0`.** Breaking for `WidgetContext` consumers (removed slices, renamed wire fields); the hook export surface is unchanged.
284
289
 
@@ -423,7 +428,7 @@ import { defineWidget, validateManifest, useDatastoreQuery, Text, View } from "@
423
428
 
424
429
  - `defineWidget({ manifest, component })` — validates the manifest and produces a widget module the host can register.
425
430
  - `validateManifest(m)` / `validatePropertySchema(s)` / `validateProps(schema, props)` — shape validation; no third-party deps.
426
- - `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, display_name, roles, group_ids }` (snake_case verbatim; `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).
431
+ - `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).
427
432
  - `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.
428
433
  - `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.
429
434
  - `WidgetContextProvider` — React context provider that the host (Studio, Player, exported app) wraps widgets with.
package/dist/contract.cjs CHANGED
@@ -235,6 +235,29 @@ const HOOKS = [
235
235
  requiredContextSlice: ["filestore.files"],
236
236
  scopes: ["files.write:*"],
237
237
  },
238
+ {
239
+ name: "usePdfExport",
240
+ signature: "usePdfExport({ spaceType, folderId? })",
241
+ description:
242
+ "Render an HTML string to a PDF server-side and SAVE it as a file in " +
243
+ "the end-user's Filestore space. The widget passes the SPACE " +
244
+ "(`{ spaceType, folderId? }`); the hook resolves owner_id from the host " +
245
+ "context and POSTs JSON through ctx.filestore.files.exportPdf. " +
246
+ "`exportToPdf(html, { fileName?, folderId? })` resolves to the created " +
247
+ "file row (mime_type `application/pdf`) or throws the wire error; a 404 " +
248
+ "means the destination folder denied a write, a 413 that the rendered " +
249
+ "PDF exceeded the size cap. The HTML is rendered by the host's PDF " +
250
+ "service (headless Chromium) — the SAME server-side pipeline on the web " +
251
+ "Player and the native export, so no browser-only PDF library is used.",
252
+ returnShape: {
253
+ exportToPdf: "(html, { fileName?, folderId? }) => Promise<FilestoreFile>",
254
+ exporting: "boolean",
255
+ error: "Error | null",
256
+ lastExported: "FilestoreFile | null",
257
+ },
258
+ requiredContextSlice: ["filestore.files"],
259
+ scopes: ["files.write:*"],
260
+ },
238
261
  {
239
262
  name: "useFilestoreFolders",
240
263
  signature: "useFilestoreFolders({ spaceType, parentFolderId?, q?, enabled? })",
@@ -975,9 +998,9 @@ const WIDGET_CONTEXT_SHAPE = {
975
998
  },
976
999
  user: {
977
1000
  description:
978
- "Signed-in user, host-provided VERBATIM (snake_case: { id, email, display_name, roles, group_ids }). Not a data-client.",
1001
+ "Signed-in user, host-built context object (camelCase: { id, email, displayName, roles, groupIds }) — NOT a wire payload, so it is the one camelCase island among the hooks. Not a data-client.",
979
1002
  required: true,
980
- fields: { id: "string", email: "string", display_name: "string" },
1003
+ fields: { id: "string", email: "string", displayName: "string" },
981
1004
  },
982
1005
  workspace: {
983
1006
  description:
@@ -1824,7 +1847,18 @@ const CONTRACT = deepFreeze({
1824
1847
  // the platform host, and an unknown slug / SSRF / timeout throws a
1825
1848
  // catchable Error. New entry in ACTION_SCRIPT_GLOBALS only — no widget
1826
1849
  // hook, primitive, manifest field, or token changed shape; minor bump.
1827
- version: "1.37.0",
1850
+ //
1851
+ // 1.38.0: additive (sc-2314) — new `usePdfExport({ spaceType, folderId? })`
1852
+ // hook. `exportToPdf(html, { fileName?, folderId? })` renders the HTML to
1853
+ // a PDF server-side (the host's headless-Chromium pipeline) and SAVES it
1854
+ // into the Filestore via `ctx.filestore.files.exportPdf`, resolving to the
1855
+ // created file row (`application/pdf`). Reuses the filestore owner_id
1856
+ // resolution + per-folder write gate; gated on the existing
1857
+ // `files.write:*` scope. The same server-side renderer backs the web
1858
+ // Player and the native export, so no browser-only PDF library is added
1859
+ // to the vetted set. No existing hook, primitive, manifest field, or
1860
+ // token changed shape — minor bump.
1861
+ version: "1.38.0",
1828
1862
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
1829
1863
  hooks: HOOKS,
1830
1864
  primitives: PRIMITIVES,
package/dist/contract.js CHANGED
@@ -235,6 +235,29 @@ const HOOKS = [
235
235
  requiredContextSlice: ["filestore.files"],
236
236
  scopes: ["files.write:*"],
237
237
  },
238
+ {
239
+ name: "usePdfExport",
240
+ signature: "usePdfExport({ spaceType, folderId? })",
241
+ description:
242
+ "Render an HTML string to a PDF server-side and SAVE it as a file in " +
243
+ "the end-user's Filestore space. The widget passes the SPACE " +
244
+ "(`{ spaceType, folderId? }`); the hook resolves owner_id from the host " +
245
+ "context and POSTs JSON through ctx.filestore.files.exportPdf. " +
246
+ "`exportToPdf(html, { fileName?, folderId? })` resolves to the created " +
247
+ "file row (mime_type `application/pdf`) or throws the wire error; a 404 " +
248
+ "means the destination folder denied a write, a 413 that the rendered " +
249
+ "PDF exceeded the size cap. The HTML is rendered by the host's PDF " +
250
+ "service (headless Chromium) — the SAME server-side pipeline on the web " +
251
+ "Player and the native export, so no browser-only PDF library is used.",
252
+ returnShape: {
253
+ exportToPdf: "(html, { fileName?, folderId? }) => Promise<FilestoreFile>",
254
+ exporting: "boolean",
255
+ error: "Error | null",
256
+ lastExported: "FilestoreFile | null",
257
+ },
258
+ requiredContextSlice: ["filestore.files"],
259
+ scopes: ["files.write:*"],
260
+ },
238
261
  {
239
262
  name: "useFilestoreFolders",
240
263
  signature: "useFilestoreFolders({ spaceType, parentFolderId?, q?, enabled? })",
@@ -975,9 +998,9 @@ const WIDGET_CONTEXT_SHAPE = {
975
998
  },
976
999
  user: {
977
1000
  description:
978
- "Signed-in user, host-provided VERBATIM (snake_case: { id, email, display_name, roles, group_ids }). Not a data-client.",
1001
+ "Signed-in user, host-built context object (camelCase: { id, email, displayName, roles, groupIds }) — NOT a wire payload, so it is the one camelCase island among the hooks. Not a data-client.",
979
1002
  required: true,
980
- fields: { id: "string", email: "string", display_name: "string" },
1003
+ fields: { id: "string", email: "string", displayName: "string" },
981
1004
  },
982
1005
  workspace: {
983
1006
  description:
@@ -1824,7 +1847,18 @@ const CONTRACT = deepFreeze({
1824
1847
  // the platform host, and an unknown slug / SSRF / timeout throws a
1825
1848
  // catchable Error. New entry in ACTION_SCRIPT_GLOBALS only — no widget
1826
1849
  // hook, primitive, manifest field, or token changed shape; minor bump.
1827
- version: "1.37.0",
1850
+ //
1851
+ // 1.38.0: additive (sc-2314) — new `usePdfExport({ spaceType, folderId? })`
1852
+ // hook. `exportToPdf(html, { fileName?, folderId? })` renders the HTML to
1853
+ // a PDF server-side (the host's headless-Chromium pipeline) and SAVES it
1854
+ // into the Filestore via `ctx.filestore.files.exportPdf`, resolving to the
1855
+ // created file row (`application/pdf`). Reuses the filestore owner_id
1856
+ // resolution + per-folder write gate; gated on the existing
1857
+ // `files.write:*` scope. The same server-side renderer backs the web
1858
+ // Player and the native export, so no browser-only PDF library is added
1859
+ // to the vetted set. No existing hook, primitive, manifest field, or
1860
+ // token changed shape — minor bump.
1861
+ version: "1.38.0",
1828
1862
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
1829
1863
  hooks: HOOKS,
1830
1864
  primitives: PRIMITIVES,
package/dist/hooks.js CHANGED
@@ -113,7 +113,7 @@ export function useWidgetStyle() {
113
113
 
114
114
  /**
115
115
  * Returns the active end-user identity VERBATIM from the host, e.g.
116
- * `{ id, email, display_name, roles, group_ids }` (snake_case fields).
116
+ * `{ id, email, displayName, roles, groupIds }` (camelCase — host-built context, not a wire payload).
117
117
  *
118
118
  * `id` is `null` for anonymous visitors (and on the Studio canvas preview,
119
119
  * which renders widgets as if signed-out so the public branch shows). The
@@ -1578,6 +1578,87 @@ export function useFilestoreUpload(options) {
1578
1578
  return { upload, uploading, error, lastUploaded };
1579
1579
  }
1580
1580
 
1581
+ /**
1582
+ * sc-2314 — render an HTML string to a PDF server-side and SAVE it into the
1583
+ * end-user's Filestore space. Returns `{ exportToPdf, exporting, error,
1584
+ * lastExported }`. The widget passes the SPACE (`{ spaceType, folderId? }`);
1585
+ * the hook resolves `owner_id` the same way the upload hook does (tenant for
1586
+ * PROJECT, app user for PERSONAL) and posts JSON to
1587
+ * `ctx.filestore.files.exportPdf`.
1588
+ *
1589
+ * `exportToPdf(html, { fileName?, folderId? })` resolves to the created file
1590
+ * record (`{ id, name, mime_type: "application/pdf", presigned_url, … }`).
1591
+ * `fileName` defaults to `document.pdf` and is forced to a `.pdf` suffix by the
1592
+ * backend. The HTML is rendered by the host's PDF service (headless Chromium)
1593
+ * — the SAME server-side pipeline backs the web Player and the native export,
1594
+ * so the capability behaves identically on both platforms (no browser-only PDF
1595
+ * library is involved).
1596
+ *
1597
+ * A 404 means the destination folder denied a write; a 413 means the rendered
1598
+ * PDF exceeded the size cap.
1599
+ */
1600
+ export function usePdfExport(options) {
1601
+ const ctx = useWidgetContextOrThrow("usePdfExport");
1602
+ if (
1603
+ !ctx.filestore ||
1604
+ !ctx.filestore.files ||
1605
+ typeof ctx.filestore.files.exportPdf !== "function"
1606
+ ) {
1607
+ throw new Error(
1608
+ "usePdfExport: host did not inject a filestore client (ctx.filestore.files.exportPdf)",
1609
+ );
1610
+ }
1611
+ const { spaceType = "project", folderId: defaultFolderId = null } = options || {};
1612
+ const ownerId = _filestoreOwnerId(ctx, spaceType);
1613
+
1614
+ const [exporting, setExporting] = useState(false);
1615
+ const [error, setError] = useState(null);
1616
+ const [lastExported, setLastExported] = useState(null);
1617
+
1618
+ const filesRef = useRef(ctx.filestore.files);
1619
+ filesRef.current = ctx.filestore.files;
1620
+
1621
+ const exportToPdf = useCallback(
1622
+ async (html, overrides) => {
1623
+ if (typeof html !== "string" || html.trim().length === 0) {
1624
+ throw new Error("usePdfExport: html is required");
1625
+ }
1626
+ if (!ownerId) {
1627
+ const err = new Error("Sign in to save a PDF");
1628
+ setError(err);
1629
+ throw err;
1630
+ }
1631
+ const folderId =
1632
+ overrides && Object.prototype.hasOwnProperty.call(overrides, "folderId")
1633
+ ? overrides.folderId
1634
+ : defaultFolderId;
1635
+ const fileName = overrides && overrides.fileName;
1636
+ const body = {
1637
+ html,
1638
+ space_type: String(spaceType || "project").toUpperCase(),
1639
+ owner_id: ownerId,
1640
+ };
1641
+ if (folderId) body.folder_id = folderId;
1642
+ if (fileName) body.file_name = fileName;
1643
+ setExporting(true);
1644
+ setError(null);
1645
+ try {
1646
+ const created = await filesRef.current.exportPdf(body);
1647
+ setLastExported(created || null);
1648
+ setExporting(false);
1649
+ return created;
1650
+ } catch (err) {
1651
+ setError(err);
1652
+ setExporting(false);
1653
+ throw err;
1654
+ }
1655
+ },
1656
+ [ownerId, defaultFolderId, spaceType],
1657
+ );
1658
+
1659
+ return { exportToPdf, exporting, error, lastExported };
1660
+ }
1661
+
1581
1662
  /**
1582
1663
  * Browse the end-user's Filestore folders. Returns { folders, loading, error,
1583
1664
  * refetch }. Mirrors useFilestoreFiles for subfolder navigation: the widget
package/dist/index.d.ts CHANGED
@@ -424,13 +424,13 @@ export interface WidgetContext<TProps = unknown> {
424
424
  * Absent / `false` everywhere the host has not opted the widget into filling.
425
425
  */
426
426
  fill?: boolean;
427
- /** Active end-user identity, snake_case verbatim. `id` is null when anonymous. */
427
+ /** Active end-user identity from the host-built context (camelCase, not a wire payload). `id` is null when anonymous. */
428
428
  user: {
429
429
  id: string | null;
430
430
  email: string | null;
431
- display_name: string | null;
431
+ displayName: string | null;
432
432
  roles: string[];
433
- group_ids: string[];
433
+ groupIds: string[];
434
434
  };
435
435
  workspace: {
436
436
  id: string;
@@ -883,9 +883,9 @@ export function useI18n(): {
883
883
  export function useUser(): {
884
884
  id: string | null;
885
885
  email: string | null;
886
- display_name: string | null;
886
+ displayName: string | null;
887
887
  roles: string[];
888
- group_ids: string[];
888
+ groupIds: string[];
889
889
  };
890
890
 
891
891
  /**
package/dist/index.js CHANGED
@@ -19,6 +19,7 @@ export {
19
19
  useAssetsByTag,
20
20
  useFilestoreFiles,
21
21
  useFilestoreUpload,
22
+ usePdfExport,
22
23
  useFilestoreFolders,
23
24
  useFileSignature,
24
25
  useFileSignatures,
@@ -19,6 +19,7 @@ export {
19
19
  useAssetsByTag,
20
20
  useFilestoreFiles,
21
21
  useFilestoreUpload,
22
+ usePdfExport,
22
23
  useFilestoreFolders,
23
24
  useFileSignature,
24
25
  useFileSignatures,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colixsystems/widget-sdk",
3
- "version": "0.53.0",
3
+ "version": "0.54.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",