@colixsystems/widget-sdk 0.267.1 → 0.268.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 +9 -1
- package/dist/contract.cjs +14 -5
- package/dist/contract.js +14 -5
- package/dist/datetimepicker-time-columns.js +14 -3
- package/dist/hooks.js +30 -2
- package/dist/index.d.ts +7 -1
- package/package.json +1 -1
- package/src/contract.js +14 -5
package/README.md
CHANGED
|
@@ -86,7 +86,15 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
86
86
|
|
|
87
87
|
## Status
|
|
88
88
|
|
|
89
|
-
`v0.
|
|
89
|
+
`v0.268.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**.
|
|
90
|
+
|
|
91
|
+
### What's new in 0.268.1 (contract 1.218.0)
|
|
92
|
+
|
|
93
|
+
**The web `DateTimePicker`'s hour and minute columns no longer look broken.** The browser drew its scrollbar inside each narrow column, which cut the chosen hour and minute off on one side. The columns now hide the scrollbar and fade their top and bottom edges to show they scroll. The keyboard focus ring is drawn in full, and a row reached with the arrow keys stops clear of the fade. No API change; native keeps the operating system's picker.
|
|
94
|
+
|
|
95
|
+
### What's new in 0.268.0 (contract 1.218.0)
|
|
96
|
+
|
|
97
|
+
**`useAction().available` tells the truth about the Action.** It used to be true whenever an Action was bound, so a widget offered a run that could never happen. It now turns false once the server says a run would not reach the script: the Action is switched off or paused, not allowed from the app, gone, or the viewer is not a signed-in app user (signed-out visitors and anonymous accounts can't run Actions). It is asked again when the binding or the signed-in user changes, and stays true while no answer has come. The actions client (`@colixsystems/actions-client` 0.2.0) gains `status(actionId)` for it.
|
|
90
98
|
|
|
91
99
|
### What's new in 0.267.1 (contract 1.217.0)
|
|
92
100
|
|
package/dist/contract.cjs
CHANGED
|
@@ -2184,7 +2184,12 @@ const HOOKS = [
|
|
|
2184
2184
|
"`actionRef` is an Action id an author bound through an `actionRef` " +
|
|
2185
2185
|
"property — when it is missing, or the host brokers no actions client, " +
|
|
2186
2186
|
"`available` is false and run() rejects UNAVAILABLE; render a visible " +
|
|
2187
|
-
"'pick an action' state, NEVER a blank box.
|
|
2187
|
+
"'pick an action' state, NEVER a blank box. sc-14224: `available` also " +
|
|
2188
|
+
"turns false once the server says a run would not reach the script — " +
|
|
2189
|
+
"the Action is switched off or paused, not allowed from the app, gone, " +
|
|
2190
|
+
"or the viewer is not a signed-in app user (signed-out visitors and " +
|
|
2191
|
+
"anonymous accounts can't run Actions) — so render an 'isn't available' " +
|
|
2192
|
+
"state then too. It stays true while no answer has come. A changed binding clears " +
|
|
2188
2193
|
"`result` and `error`, and a run that settles after the binding changed " +
|
|
2189
2194
|
"(or after unmount) writes nothing. ActionError.code: " +
|
|
2190
2195
|
"INVALID_ACTION_INPUT (400) — the input was refused; " +
|
|
@@ -2195,7 +2200,7 @@ const HOOKS = [
|
|
|
2195
2200
|
"MARKDOWN, so render it with <RichText value={result} />, not <Text>. " +
|
|
2196
2201
|
"Reads ctx.actions.",
|
|
2197
2202
|
returnShape: {
|
|
2198
|
-
available: "boolean // false when no Action is bound,
|
|
2203
|
+
available: "boolean // false when no Action is bound, the host brokers none, or the server says a run would not reach the script",
|
|
2199
2204
|
run: "(input) => Promise<unknown> // input is optional; resolves with the Action's respond() value, or null; rejects with ActionError",
|
|
2200
2205
|
running: "boolean // true while any run is in flight",
|
|
2201
2206
|
result: "unknown // the last successful run's result; null before the first run",
|
|
@@ -4150,13 +4155,14 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
4150
4155
|
actions: {
|
|
4151
4156
|
description:
|
|
4152
4157
|
"sc-13843 — injected @colixsystems/actions-client instance. " +
|
|
4153
|
-
"{ run(actionId, input?) -> Promise<{ ok, result }
|
|
4158
|
+
"{ run(actionId, input?) -> Promise<{ ok, result }>, status(actionId) -> " +
|
|
4159
|
+
"Promise<{ available, reason }> }. Backs useAction(). " +
|
|
4154
4160
|
"Runs one Action as the signed-in caller; the server refuses an Action " +
|
|
4155
4161
|
"without the app trigger. Errors carry the server's `code` and `status`. " +
|
|
4156
4162
|
"Absent on a host that brokers no actions client, where useAction() " +
|
|
4157
4163
|
"degrades to available: false rather than throwing.",
|
|
4158
4164
|
required: false,
|
|
4159
|
-
fields: { run: "function" },
|
|
4165
|
+
fields: { run: "function", status: "function" },
|
|
4160
4166
|
},
|
|
4161
4167
|
linkPreviews: {
|
|
4162
4168
|
description:
|
|
@@ -6472,7 +6478,10 @@ const CONTRACT = deepFreeze({
|
|
|
6472
6478
|
// 1.217.0: additive (sc-4747) — `TextInput` documents `keyboardType`,
|
|
6473
6479
|
// `autoCapitalize`, `autoCorrect`, `autoComplete`, `secureTextEntry`;
|
|
6474
6480
|
// `useDatastoreSchema` columns document `text_format` (snake_case wire).
|
|
6475
|
-
|
|
6481
|
+
// 1.218.0: additive (sc-14224) — the actions client's `status(actionId)`;
|
|
6482
|
+
// `useAction().available` turns false when the server says a run would
|
|
6483
|
+
// not reach the script.
|
|
6484
|
+
version: "1.218.0",
|
|
6476
6485
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6477
6486
|
hooks: HOOKS,
|
|
6478
6487
|
primitives: PRIMITIVES,
|
package/dist/contract.js
CHANGED
|
@@ -2181,7 +2181,12 @@ const HOOKS = [
|
|
|
2181
2181
|
"`actionRef` is an Action id an author bound through an `actionRef` " +
|
|
2182
2182
|
"property — when it is missing, or the host brokers no actions client, " +
|
|
2183
2183
|
"`available` is false and run() rejects UNAVAILABLE; render a visible " +
|
|
2184
|
-
"'pick an action' state, NEVER a blank box.
|
|
2184
|
+
"'pick an action' state, NEVER a blank box. sc-14224: `available` also " +
|
|
2185
|
+
"turns false once the server says a run would not reach the script — " +
|
|
2186
|
+
"the Action is switched off or paused, not allowed from the app, gone, " +
|
|
2187
|
+
"or the viewer is not a signed-in app user (signed-out visitors and " +
|
|
2188
|
+
"anonymous accounts can't run Actions) — so render an 'isn't available' " +
|
|
2189
|
+
"state then too. It stays true while no answer has come. A changed binding clears " +
|
|
2185
2190
|
"`result` and `error`, and a run that settles after the binding changed " +
|
|
2186
2191
|
"(or after unmount) writes nothing. ActionError.code: " +
|
|
2187
2192
|
"INVALID_ACTION_INPUT (400) — the input was refused; " +
|
|
@@ -2192,7 +2197,7 @@ const HOOKS = [
|
|
|
2192
2197
|
"MARKDOWN, so render it with <RichText value={result} />, not <Text>. " +
|
|
2193
2198
|
"Reads ctx.actions.",
|
|
2194
2199
|
returnShape: {
|
|
2195
|
-
available: "boolean // false when no Action is bound,
|
|
2200
|
+
available: "boolean // false when no Action is bound, the host brokers none, or the server says a run would not reach the script",
|
|
2196
2201
|
run: "(input) => Promise<unknown> // input is optional; resolves with the Action's respond() value, or null; rejects with ActionError",
|
|
2197
2202
|
running: "boolean // true while any run is in flight",
|
|
2198
2203
|
result: "unknown // the last successful run's result; null before the first run",
|
|
@@ -4147,13 +4152,14 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
4147
4152
|
actions: {
|
|
4148
4153
|
description:
|
|
4149
4154
|
"sc-13843 — injected @colixsystems/actions-client instance. " +
|
|
4150
|
-
"{ run(actionId, input?) -> Promise<{ ok, result }
|
|
4155
|
+
"{ run(actionId, input?) -> Promise<{ ok, result }>, status(actionId) -> " +
|
|
4156
|
+
"Promise<{ available, reason }> }. Backs useAction(). " +
|
|
4151
4157
|
"Runs one Action as the signed-in caller; the server refuses an Action " +
|
|
4152
4158
|
"without the app trigger. Errors carry the server's `code` and `status`. " +
|
|
4153
4159
|
"Absent on a host that brokers no actions client, where useAction() " +
|
|
4154
4160
|
"degrades to available: false rather than throwing.",
|
|
4155
4161
|
required: false,
|
|
4156
|
-
fields: { run: "function" },
|
|
4162
|
+
fields: { run: "function", status: "function" },
|
|
4157
4163
|
},
|
|
4158
4164
|
linkPreviews: {
|
|
4159
4165
|
description:
|
|
@@ -6469,7 +6475,10 @@ const CONTRACT = deepFreeze({
|
|
|
6469
6475
|
// 1.217.0: additive (sc-4747) — `TextInput` documents `keyboardType`,
|
|
6470
6476
|
// `autoCapitalize`, `autoCorrect`, `autoComplete`, `secureTextEntry`;
|
|
6471
6477
|
// `useDatastoreSchema` columns document `text_format` (snake_case wire).
|
|
6472
|
-
|
|
6478
|
+
// 1.218.0: additive (sc-14224) — the actions client's `status(actionId)`;
|
|
6479
|
+
// `useAction().available` turns false when the server says a run would
|
|
6480
|
+
// not reach the script.
|
|
6481
|
+
version: "1.218.0",
|
|
6473
6482
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6474
6483
|
hooks: HOOKS,
|
|
6475
6484
|
primitives: PRIMITIVES,
|
|
@@ -14,6 +14,9 @@ const COLUMN_WIDTH = 64;
|
|
|
14
14
|
const HOURS = Array.from({ length: 24 }, (_, i) => i);
|
|
15
15
|
const MINUTES = Array.from({ length: 60 }, (_, i) => i);
|
|
16
16
|
const LIST_KEYS = ["ArrowDown", "ArrowUp", "Home", "End"];
|
|
17
|
+
// Fades the edges in place of a scrollbar, which would clip cells.
|
|
18
|
+
const FADE = 24;
|
|
19
|
+
const EDGE_FADE = `linear-gradient(to bottom, transparent, #000 ${FADE}px, #000 calc(100% - ${FADE}px), transparent)`;
|
|
17
20
|
|
|
18
21
|
/** @param {{View: any, Text: any, Pressable: any, ScrollView: any, Platform: any}} rn */
|
|
19
22
|
export function makeTimeColumns(rn) {
|
|
@@ -27,7 +30,7 @@ export function makeTimeColumns(rn) {
|
|
|
27
30
|
React.useEffect(() => {
|
|
28
31
|
if (!mounted.current) {
|
|
29
32
|
mounted.current = true;
|
|
30
|
-
const top = values.indexOf(chosen) * (ITEM_HEIGHT + ITEM_GAP) - (height - ITEM_HEIGHT) / 2;
|
|
33
|
+
const top = FADE + values.indexOf(chosen) * (ITEM_HEIGHT + ITEM_GAP) - (height - ITEM_HEIGHT) / 2;
|
|
31
34
|
if (scroller.current && typeof scroller.current.scrollTo === "function") {
|
|
32
35
|
scroller.current.scrollTo({ y: Math.max(0, top), animated: false });
|
|
33
36
|
}
|
|
@@ -76,7 +79,7 @@ export function makeTimeColumns(rn) {
|
|
|
76
79
|
"aria-selected": selected,
|
|
77
80
|
disabled: option.disabled,
|
|
78
81
|
testID: testID ? `${testID}-${field}-${twoDigits(option.value)}` : undefined,
|
|
79
|
-
style: cellStyle(t, { selected, current: false, disabled: option.disabled, width: COLUMN_WIDTH, height: ITEM_HEIGHT, round: true }),
|
|
82
|
+
style: cellStyle(t, { selected, current: false, disabled: option.disabled, width: COLUMN_WIDTH - 2 * ITEM_GAP, height: ITEM_HEIGHT, round: true }),
|
|
80
83
|
},
|
|
81
84
|
h(Text, { style: cellText(t, { selected }) }, twoDigits(option.value)),
|
|
82
85
|
);
|
|
@@ -87,7 +90,15 @@ export function makeTimeColumns(rn) {
|
|
|
87
90
|
h(Text, { "aria-hidden": true, style: { textAlign: "center", fontFamily: t.fontFamily, fontSize: t.helperSize, fontWeight: "500", color: t.helperColor } }, name),
|
|
88
91
|
h(
|
|
89
92
|
ScrollView,
|
|
90
|
-
{
|
|
93
|
+
{
|
|
94
|
+
ref: scroller,
|
|
95
|
+
role: "listbox",
|
|
96
|
+
accessibilityLabel: name,
|
|
97
|
+
showsVerticalScrollIndicator: false,
|
|
98
|
+
style: { height, maskImage: EDGE_FADE, WebkitMaskImage: EDGE_FADE, scrollPaddingBlock: FADE },
|
|
99
|
+
// The first and last rows can scroll clear of the fade.
|
|
100
|
+
contentContainerStyle: { gap: ITEM_GAP, paddingVertical: FADE, paddingHorizontal: ITEM_GAP },
|
|
101
|
+
},
|
|
91
102
|
...options.map(item),
|
|
92
103
|
),
|
|
93
104
|
);
|
package/dist/hooks.js
CHANGED
|
@@ -3893,18 +3893,46 @@ function toActionError(err) {
|
|
|
3893
3893
|
* `available` is false and `run` rejects UNAVAILABLE rather than throwing at
|
|
3894
3894
|
* render — the widget must render a visible unbound state (CLAUDE.md §8).
|
|
3895
3895
|
*
|
|
3896
|
+
* sc-14224 — `available` also turns false once the server says a run would
|
|
3897
|
+
* not reach the script: the Action is switched off, paused, not allowed from
|
|
3898
|
+
* the app, gone, or the viewer is not a signed-in app user. It is asked again
|
|
3899
|
+
* when the binding or the signed-in user changes. With no answer (offline, an
|
|
3900
|
+
* older host) it stays true and `run` reports the refusal.
|
|
3901
|
+
*
|
|
3896
3902
|
* Routes through the injected `@colixsystems/actions-client` at `ctx.actions`,
|
|
3897
3903
|
* so the Player and the Expo export resolve against the identical client.
|
|
3898
3904
|
*/
|
|
3899
3905
|
export function useAction(actionRef) {
|
|
3900
3906
|
const ctx = useWidgetContextOrThrow("useAction");
|
|
3901
|
-
const
|
|
3907
|
+
const bound =
|
|
3902
3908
|
Boolean(actionRef) &&
|
|
3903
3909
|
Boolean(ctx.actions) &&
|
|
3904
3910
|
typeof ctx.actions.run === "function";
|
|
3911
|
+
const canAsk = bound && typeof ctx.actions.status === "function";
|
|
3912
|
+
const askedAs = `${actionRef}|${(ctx.user && ctx.user.id) || ""}`;
|
|
3905
3913
|
|
|
3906
3914
|
const clientRef = useRef(null);
|
|
3907
|
-
clientRef.current =
|
|
3915
|
+
clientRef.current = bound ? ctx.actions : null;
|
|
3916
|
+
|
|
3917
|
+
// The binding and viewer the server last refused, so a rebind starts fresh.
|
|
3918
|
+
const [refusedAs, setRefusedAs] = useState(null);
|
|
3919
|
+
useEffect(() => {
|
|
3920
|
+
const client = clientRef.current;
|
|
3921
|
+
if (!canAsk || !client) return undefined;
|
|
3922
|
+
let live = true;
|
|
3923
|
+
client.status(actionRef).then(
|
|
3924
|
+
(answer) => {
|
|
3925
|
+
if (live) setRefusedAs(answer && answer.available === false ? askedAs : null);
|
|
3926
|
+
},
|
|
3927
|
+
// No answer is not a refusal: keep offering the run.
|
|
3928
|
+
() => {},
|
|
3929
|
+
);
|
|
3930
|
+
return () => {
|
|
3931
|
+
live = false;
|
|
3932
|
+
};
|
|
3933
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
3934
|
+
}, [askedAs, canAsk]);
|
|
3935
|
+
const available = bound && refusedAs !== askedAs;
|
|
3908
3936
|
const actionIdRef = useRef(actionRef);
|
|
3909
3937
|
actionIdRef.current = actionRef;
|
|
3910
3938
|
|
package/dist/index.d.ts
CHANGED
|
@@ -890,6 +890,8 @@ export interface WidgetContext<TProps = unknown> {
|
|
|
890
890
|
/** sc-13843 — injected @colixsystems/actions-client backing useAction(). */
|
|
891
891
|
actions?: {
|
|
892
892
|
run(actionId: string, input?: Record<string, unknown> | null): Promise<{ ok: boolean; result: unknown }>;
|
|
893
|
+
/** sc-14224 — whether a run would reach the script; optional on older hosts. */
|
|
894
|
+
status?(actionId: string): Promise<{ available: boolean; reason: string | null }>;
|
|
893
895
|
};
|
|
894
896
|
/** Injected @colixsystems/link-preview-client backing useLinkPreview(). */
|
|
895
897
|
linkPreviews?: { get(url: string): Promise<LinkPreview> };
|
|
@@ -1893,7 +1895,11 @@ export interface AgentApi {
|
|
|
1893
1895
|
export function useAgent(agentRef: string | null | undefined): AgentApi;
|
|
1894
1896
|
|
|
1895
1897
|
export interface ActionApi {
|
|
1896
|
-
/**
|
|
1898
|
+
/**
|
|
1899
|
+
* False when no Action is bound, the host brokers no actions client, or the
|
|
1900
|
+
* server says a run would not reach the script (switched off, not allowed
|
|
1901
|
+
* from the app, or the viewer is not a signed-in app user).
|
|
1902
|
+
*/
|
|
1897
1903
|
available: boolean;
|
|
1898
1904
|
/**
|
|
1899
1905
|
* Imperative — call from an event handler, never on mount. Resolves with the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.268.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",
|
package/src/contract.js
CHANGED
|
@@ -2181,7 +2181,12 @@ const HOOKS = [
|
|
|
2181
2181
|
"`actionRef` is an Action id an author bound through an `actionRef` " +
|
|
2182
2182
|
"property — when it is missing, or the host brokers no actions client, " +
|
|
2183
2183
|
"`available` is false and run() rejects UNAVAILABLE; render a visible " +
|
|
2184
|
-
"'pick an action' state, NEVER a blank box.
|
|
2184
|
+
"'pick an action' state, NEVER a blank box. sc-14224: `available` also " +
|
|
2185
|
+
"turns false once the server says a run would not reach the script — " +
|
|
2186
|
+
"the Action is switched off or paused, not allowed from the app, gone, " +
|
|
2187
|
+
"or the viewer is not a signed-in app user (signed-out visitors and " +
|
|
2188
|
+
"anonymous accounts can't run Actions) — so render an 'isn't available' " +
|
|
2189
|
+
"state then too. It stays true while no answer has come. A changed binding clears " +
|
|
2185
2190
|
"`result` and `error`, and a run that settles after the binding changed " +
|
|
2186
2191
|
"(or after unmount) writes nothing. ActionError.code: " +
|
|
2187
2192
|
"INVALID_ACTION_INPUT (400) — the input was refused; " +
|
|
@@ -2192,7 +2197,7 @@ const HOOKS = [
|
|
|
2192
2197
|
"MARKDOWN, so render it with <RichText value={result} />, not <Text>. " +
|
|
2193
2198
|
"Reads ctx.actions.",
|
|
2194
2199
|
returnShape: {
|
|
2195
|
-
available: "boolean // false when no Action is bound,
|
|
2200
|
+
available: "boolean // false when no Action is bound, the host brokers none, or the server says a run would not reach the script",
|
|
2196
2201
|
run: "(input) => Promise<unknown> // input is optional; resolves with the Action's respond() value, or null; rejects with ActionError",
|
|
2197
2202
|
running: "boolean // true while any run is in flight",
|
|
2198
2203
|
result: "unknown // the last successful run's result; null before the first run",
|
|
@@ -4147,13 +4152,14 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
4147
4152
|
actions: {
|
|
4148
4153
|
description:
|
|
4149
4154
|
"sc-13843 — injected @colixsystems/actions-client instance. " +
|
|
4150
|
-
"{ run(actionId, input?) -> Promise<{ ok, result }
|
|
4155
|
+
"{ run(actionId, input?) -> Promise<{ ok, result }>, status(actionId) -> " +
|
|
4156
|
+
"Promise<{ available, reason }> }. Backs useAction(). " +
|
|
4151
4157
|
"Runs one Action as the signed-in caller; the server refuses an Action " +
|
|
4152
4158
|
"without the app trigger. Errors carry the server's `code` and `status`. " +
|
|
4153
4159
|
"Absent on a host that brokers no actions client, where useAction() " +
|
|
4154
4160
|
"degrades to available: false rather than throwing.",
|
|
4155
4161
|
required: false,
|
|
4156
|
-
fields: { run: "function" },
|
|
4162
|
+
fields: { run: "function", status: "function" },
|
|
4157
4163
|
},
|
|
4158
4164
|
linkPreviews: {
|
|
4159
4165
|
description:
|
|
@@ -6469,7 +6475,10 @@ const CONTRACT = deepFreeze({
|
|
|
6469
6475
|
// 1.217.0: additive (sc-4747) — `TextInput` documents `keyboardType`,
|
|
6470
6476
|
// `autoCapitalize`, `autoCorrect`, `autoComplete`, `secureTextEntry`;
|
|
6471
6477
|
// `useDatastoreSchema` columns document `text_format` (snake_case wire).
|
|
6472
|
-
|
|
6478
|
+
// 1.218.0: additive (sc-14224) — the actions client's `status(actionId)`;
|
|
6479
|
+
// `useAction().available` turns false when the server says a run would
|
|
6480
|
+
// not reach the script.
|
|
6481
|
+
version: "1.218.0",
|
|
6473
6482
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6474
6483
|
hooks: HOOKS,
|
|
6475
6484
|
primitives: PRIMITIVES,
|