@colixsystems/widget-sdk 0.260.0 → 0.262.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 +36 -15
- package/dist/accordion.js +2 -1
- package/dist/banner.js +1 -1
- package/dist/button.js +1 -1
- package/dist/card-photo.js +1 -1
- package/dist/card.js +1 -1
- package/dist/chart.js +1 -1
- package/dist/checkbox.js +1 -1
- package/dist/chip.js +1 -1
- package/dist/confirm-dialog.js +1 -1
- package/dist/contract.cjs +53 -2
- package/dist/contract.js +53 -2
- package/dist/data-table.js +1 -1
- package/dist/datetimepicker.js +2 -1
- package/dist/detail-parts.js +1 -1
- package/dist/emoji-picker.js +1 -1
- package/dist/file-image.js +1 -1
- package/dist/file-upload.js +2 -1
- package/dist/filter-bar.js +1 -1
- package/dist/form.js +1 -1
- package/dist/gradient.js +1 -1
- package/dist/grid.js +1 -1
- package/dist/hooks.js +134 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +2 -0
- package/dist/index.native.js +2 -0
- package/dist/linter.cjs +11 -0
- package/dist/linter.js +11 -0
- package/dist/list.js +1 -1
- package/dist/loading.js +1 -1
- package/dist/markdown-input.js +1 -1
- package/dist/modal.js +1 -1
- package/dist/overlay.js +1 -1
- package/dist/people-picker.js +1 -1
- package/dist/popover.js +1 -1
- package/dist/primitives.js +1 -1
- package/dist/property-schema.js +4 -0
- package/dist/radio-group.js +1 -1
- package/dist/richtext.js +1 -1
- package/dist/search-field.js +1 -1
- package/dist/select.js +1 -1
- package/dist/sortable.js +1 -1
- package/dist/switch.js +1 -1
- package/dist/tabs.js +1 -1
- package/dist/text-field.js +1 -1
- package/dist/text-scale-style.js +60 -0
- package/dist/text-scale.d.ts +11 -0
- package/dist/text-scale.js +40 -0
- package/dist/video.js +1 -1
- package/package.json +6 -1
- package/src/contract.js +53 -2
package/dist/hooks.js
CHANGED
|
@@ -3843,6 +3843,140 @@ export function useAgent(agentRef) {
|
|
|
3843
3843
|
};
|
|
3844
3844
|
}
|
|
3845
3845
|
|
|
3846
|
+
/**
|
|
3847
|
+
* sc-13843 — structured error thrown by `useAction().run`. Carries a stable
|
|
3848
|
+
* `code` so a widget branches without parsing message strings.
|
|
3849
|
+
*
|
|
3850
|
+
* `code` is one of:
|
|
3851
|
+
* - "UNAVAILABLE" — no Action is bound, or the host brokers no
|
|
3852
|
+
* actions client (render the unbound state)
|
|
3853
|
+
* - "INVALID_ACTION_INPUT" — 400 (the input is not plain JSON within the
|
|
3854
|
+
* limits, or the Action's own check refused it)
|
|
3855
|
+
* - "ACTION_NOT_APP_INVOKABLE" — 403 (the workspace has not let the app run
|
|
3856
|
+
* this Action; retrying will not help)
|
|
3857
|
+
* - "ACTION_NOT_FOUND" — 404 (the bound Action was deleted)
|
|
3858
|
+
* - "RATE_LIMITED" — 429 (too many runs — retry shortly)
|
|
3859
|
+
* - "AUTH_REQUIRED" — 401 (no signed-in app user)
|
|
3860
|
+
* - "TOKEN_EXPIRED" / "TOKEN_INVALID" — 401 (the app user's session ended)
|
|
3861
|
+
* - "ACTION_FAILED" — the Action ran and failed, or no answer came
|
|
3862
|
+
*
|
|
3863
|
+
* The actions client owns these codes; any other server code passes through.
|
|
3864
|
+
*/
|
|
3865
|
+
export class ActionError extends Error {
|
|
3866
|
+
constructor(code, message, opts) {
|
|
3867
|
+
super(message);
|
|
3868
|
+
this.name = "ActionError";
|
|
3869
|
+
this.code = code;
|
|
3870
|
+
if (opts && opts.cause) this.cause = opts.cause;
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
|
|
3874
|
+
function toActionError(err) {
|
|
3875
|
+
if (err instanceof ActionError) return err;
|
|
3876
|
+
const code = err && typeof err.code === "string" && err.code ? err.code : "ACTION_FAILED";
|
|
3877
|
+
const message = (err && err.message) || "The action could not run";
|
|
3878
|
+
return new ActionError(code, message, { cause: err });
|
|
3879
|
+
}
|
|
3880
|
+
|
|
3881
|
+
/**
|
|
3882
|
+
* sc-13843 — run one of the workspace's Actions and read what it returned.
|
|
3883
|
+
* Returns `{ available, run, running, result, error }`.
|
|
3884
|
+
*
|
|
3885
|
+
* run(input?) → Promise<result> the Action's `respond()` value, or null
|
|
3886
|
+
*
|
|
3887
|
+
* IMPERATIVE — it never runs on mount or render; call `run` from an event
|
|
3888
|
+
* handler. An Action can write data and send messages, so running it because
|
|
3889
|
+
* a widget was placed would be a side effect nobody asked for.
|
|
3890
|
+
*
|
|
3891
|
+
* `actionRef` is the Action id an author bound through an `actionRef`
|
|
3892
|
+
* property. When it is absent, or the host brokers no actions client,
|
|
3893
|
+
* `available` is false and `run` rejects UNAVAILABLE rather than throwing at
|
|
3894
|
+
* render — the widget must render a visible unbound state (CLAUDE.md §8).
|
|
3895
|
+
*
|
|
3896
|
+
* Routes through the injected `@colixsystems/actions-client` at `ctx.actions`,
|
|
3897
|
+
* so the Player and the Expo export resolve against the identical client.
|
|
3898
|
+
*/
|
|
3899
|
+
export function useAction(actionRef) {
|
|
3900
|
+
const ctx = useWidgetContextOrThrow("useAction");
|
|
3901
|
+
const available =
|
|
3902
|
+
Boolean(actionRef) &&
|
|
3903
|
+
Boolean(ctx.actions) &&
|
|
3904
|
+
typeof ctx.actions.run === "function";
|
|
3905
|
+
|
|
3906
|
+
const clientRef = useRef(null);
|
|
3907
|
+
clientRef.current = available ? ctx.actions : null;
|
|
3908
|
+
const actionIdRef = useRef(actionRef);
|
|
3909
|
+
actionIdRef.current = actionRef;
|
|
3910
|
+
|
|
3911
|
+
// Bumped in render on a rebind, so an A→B→A rebind never resurfaces A's result.
|
|
3912
|
+
const generationRef = useRef(0);
|
|
3913
|
+
const boundRef = useRef(actionRef);
|
|
3914
|
+
if (boundRef.current !== actionRef) {
|
|
3915
|
+
boundRef.current = actionRef;
|
|
3916
|
+
generationRef.current += 1;
|
|
3917
|
+
}
|
|
3918
|
+
|
|
3919
|
+
const mountedRef = useRef(true);
|
|
3920
|
+
useEffect(() => {
|
|
3921
|
+
mountedRef.current = true;
|
|
3922
|
+
return () => {
|
|
3923
|
+
mountedRef.current = false;
|
|
3924
|
+
};
|
|
3925
|
+
}, []);
|
|
3926
|
+
|
|
3927
|
+
const latestRunRef = useRef(0);
|
|
3928
|
+
// `pending` counts this binding's runs, so an old binding's run never shows.
|
|
3929
|
+
const [outcome, setOutcome] = useState({ generation: 0, pending: 0, result: null, error: null });
|
|
3930
|
+
|
|
3931
|
+
const run = useCallback(async (input) => {
|
|
3932
|
+
const client = clientRef.current;
|
|
3933
|
+
const actionId = actionIdRef.current;
|
|
3934
|
+
if (!client || !actionId) {
|
|
3935
|
+
throw new ActionError("UNAVAILABLE", "No action is available for this host");
|
|
3936
|
+
}
|
|
3937
|
+
const generation = generationRef.current;
|
|
3938
|
+
const runId = ++latestRunRef.current;
|
|
3939
|
+
// Only the newest run on the shown binding may write its outcome.
|
|
3940
|
+
const settle = (next) => {
|
|
3941
|
+
if (!mountedRef.current) return;
|
|
3942
|
+
const latest = runId === latestRunRef.current;
|
|
3943
|
+
setOutcome((prior) => {
|
|
3944
|
+
if (prior.generation !== generation) return prior;
|
|
3945
|
+
const pending = Math.max(0, prior.pending - 1);
|
|
3946
|
+
return latest ? { ...prior, ...next, pending } : { ...prior, pending };
|
|
3947
|
+
});
|
|
3948
|
+
};
|
|
3949
|
+
|
|
3950
|
+
setOutcome((prior) =>
|
|
3951
|
+
prior.generation === generation
|
|
3952
|
+
? { ...prior, pending: prior.pending + 1, error: null }
|
|
3953
|
+
: { generation, pending: 1, result: null, error: null },
|
|
3954
|
+
);
|
|
3955
|
+
try {
|
|
3956
|
+
const response = await client.run(actionId, input);
|
|
3957
|
+
if (!response || response.ok !== true) {
|
|
3958
|
+
throw new ActionError("ACTION_FAILED", "The action did not complete");
|
|
3959
|
+
}
|
|
3960
|
+
const value = response.result === undefined ? null : response.result;
|
|
3961
|
+
settle({ result: value, error: null });
|
|
3962
|
+
return value;
|
|
3963
|
+
} catch (err) {
|
|
3964
|
+
const e = toActionError(err);
|
|
3965
|
+
settle({ error: e });
|
|
3966
|
+
throw e;
|
|
3967
|
+
}
|
|
3968
|
+
}, []);
|
|
3969
|
+
|
|
3970
|
+
const current = outcome.generation === generationRef.current;
|
|
3971
|
+
return {
|
|
3972
|
+
available,
|
|
3973
|
+
run,
|
|
3974
|
+
running: current && outcome.pending > 0,
|
|
3975
|
+
result: current ? outcome.result : null,
|
|
3976
|
+
error: current ? outcome.error : null,
|
|
3977
|
+
};
|
|
3978
|
+
}
|
|
3979
|
+
|
|
3846
3980
|
/**
|
|
3847
3981
|
* Datastore mutation hook. Returns { create, update, delete }, each method
|
|
3848
3982
|
* returning a Promise. Routes through the injected
|
package/dist/index.d.ts
CHANGED
|
@@ -885,6 +885,10 @@ export interface WidgetContext<TProps = unknown> {
|
|
|
885
885
|
identification: IdentificationClient;
|
|
886
886
|
/** sc-7991 — injected @colixsystems/agents-client backing useAgent(). */
|
|
887
887
|
agents?: { conversations(agentId: string): unknown };
|
|
888
|
+
/** sc-13843 — injected @colixsystems/actions-client backing useAction(). */
|
|
889
|
+
actions?: {
|
|
890
|
+
run(actionId: string, input?: Record<string, unknown> | null): Promise<{ ok: boolean; result: unknown }>;
|
|
891
|
+
};
|
|
888
892
|
/** Injected @colixsystems/link-preview-client backing useLinkPreview(). */
|
|
889
893
|
linkPreviews?: { get(url: string): Promise<LinkPreview> };
|
|
890
894
|
/** Host child-node renderer; backs WidgetTree / useChildRenderer. */
|
|
@@ -1858,6 +1862,28 @@ export interface AgentApi {
|
|
|
1858
1862
|
*/
|
|
1859
1863
|
export function useAgent(agentRef: string | null | undefined): AgentApi;
|
|
1860
1864
|
|
|
1865
|
+
export interface ActionApi {
|
|
1866
|
+
/** False when no Action is bound, or the host brokers no actions client. */
|
|
1867
|
+
available: boolean;
|
|
1868
|
+
/**
|
|
1869
|
+
* Imperative — call from an event handler, never on mount. Resolves with the
|
|
1870
|
+
* value the Action passed to `respond()`, or null; rejects with ActionError.
|
|
1871
|
+
*/
|
|
1872
|
+
run(input?: Record<string, unknown> | null): Promise<unknown>;
|
|
1873
|
+
/** True while any run is in flight. */
|
|
1874
|
+
running: boolean;
|
|
1875
|
+
/** The last successful run's result; cleared when the binding changes. */
|
|
1876
|
+
result: unknown;
|
|
1877
|
+
error: ActionError | null;
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
/**
|
|
1881
|
+
* sc-13843 — run one of the workspace's Actions and read its result. The
|
|
1882
|
+
* Action must have the app trigger; the widget needs `actions.run:*` in
|
|
1883
|
+
* `requestedScopes`. Never runs on mount.
|
|
1884
|
+
*/
|
|
1885
|
+
export function useAction(actionRef: string | null | undefined): ActionApi;
|
|
1886
|
+
|
|
1861
1887
|
// REQ-RT-07 realtime subscription transport state.
|
|
1862
1888
|
export type DatastoreSubscriptionStatus =
|
|
1863
1889
|
| "connecting"
|
|
@@ -3140,6 +3166,27 @@ export class AgentError extends Error {
|
|
|
3140
3166
|
constructor(code: string, message: string, opts?: { cause?: unknown });
|
|
3141
3167
|
}
|
|
3142
3168
|
|
|
3169
|
+
/**
|
|
3170
|
+
* sc-13843 — structured error thrown by `useAction().run`. Branch on `code`:
|
|
3171
|
+
* `ACTION_NOT_APP_INVOKABLE` and `ACTION_NOT_FOUND` will not clear on a retry,
|
|
3172
|
+
* `RATE_LIMITED` will. `AUTH_REQUIRED`, `TOKEN_EXPIRED` and `TOKEN_INVALID`
|
|
3173
|
+
* mean the app user must sign in. Never show an app user the raw message.
|
|
3174
|
+
*/
|
|
3175
|
+
export class ActionError extends Error {
|
|
3176
|
+
code:
|
|
3177
|
+
| "UNAVAILABLE"
|
|
3178
|
+
| "INVALID_ACTION_INPUT"
|
|
3179
|
+
| "ACTION_NOT_APP_INVOKABLE"
|
|
3180
|
+
| "ACTION_NOT_FOUND"
|
|
3181
|
+
| "RATE_LIMITED"
|
|
3182
|
+
| "AUTH_REQUIRED"
|
|
3183
|
+
| "TOKEN_EXPIRED"
|
|
3184
|
+
| "TOKEN_INVALID"
|
|
3185
|
+
| "ACTION_FAILED"
|
|
3186
|
+
| (string & {});
|
|
3187
|
+
constructor(code: string, message: string, opts?: { cause?: unknown });
|
|
3188
|
+
}
|
|
3189
|
+
|
|
3143
3190
|
/**
|
|
3144
3191
|
* Error surfaced by `useLinkPreview().error`. A page that simply has no
|
|
3145
3192
|
* preview is not an error. `retryAfter` (seconds) is set on RATE_LIMITED.
|
package/dist/index.js
CHANGED
|
@@ -29,6 +29,7 @@ export {
|
|
|
29
29
|
DirectoryError,
|
|
30
30
|
PermissionError,
|
|
31
31
|
AgentError,
|
|
32
|
+
ActionError,
|
|
32
33
|
useDatastoreQuery,
|
|
33
34
|
useDatastoreRecord,
|
|
34
35
|
useDatastoreSchema,
|
|
@@ -37,6 +38,7 @@ export {
|
|
|
37
38
|
useCanWrite,
|
|
38
39
|
useInterpretDraft,
|
|
39
40
|
useAgent,
|
|
41
|
+
useAction,
|
|
40
42
|
useAsset,
|
|
41
43
|
useAssetsByTag,
|
|
42
44
|
useBundledAsset,
|
package/dist/index.native.js
CHANGED
|
@@ -29,6 +29,7 @@ export {
|
|
|
29
29
|
DirectoryError,
|
|
30
30
|
PermissionError,
|
|
31
31
|
AgentError,
|
|
32
|
+
ActionError,
|
|
32
33
|
useDatastoreQuery,
|
|
33
34
|
useDatastoreRecord,
|
|
34
35
|
useDatastoreSchema,
|
|
@@ -37,6 +38,7 @@ export {
|
|
|
37
38
|
useCanWrite,
|
|
38
39
|
useInterpretDraft,
|
|
39
40
|
useAgent,
|
|
41
|
+
useAction,
|
|
40
42
|
useAsset,
|
|
41
43
|
useAssetsByTag,
|
|
42
44
|
useBundledAsset,
|
package/dist/linter.cjs
CHANGED
|
@@ -740,6 +740,17 @@ function _scopeRules(source, manifest) {
|
|
|
740
740
|
});
|
|
741
741
|
}
|
|
742
742
|
}
|
|
743
|
+
|
|
744
|
+
// sc-13843 — DISCLOSURE, like ai.invoke: the server gate is the app-user JWT
|
|
745
|
+
// plus the Action's app trigger, never this scope.
|
|
746
|
+
if (/\buseAction\s*\(/.test(source) && !declared.has("actions.run:*")) {
|
|
747
|
+
findings.push({
|
|
748
|
+
rule: "scope-required-for-useAction",
|
|
749
|
+
label: "useAction() requires `actions.run:*` in manifest.requestedScopes",
|
|
750
|
+
line: 0,
|
|
751
|
+
snippet: "",
|
|
752
|
+
});
|
|
753
|
+
}
|
|
743
754
|
const lines = source.split(/\r?\n/);
|
|
744
755
|
let firedUsersWrite = false;
|
|
745
756
|
let firedUsersDelete = false;
|
package/dist/linter.js
CHANGED
|
@@ -695,6 +695,17 @@ function _scopeRules(source, manifest) {
|
|
|
695
695
|
});
|
|
696
696
|
}
|
|
697
697
|
}
|
|
698
|
+
|
|
699
|
+
// sc-13843 — DISCLOSURE, like ai.invoke: the server gate is the app-user JWT
|
|
700
|
+
// plus the Action's app trigger, never this scope.
|
|
701
|
+
if (/\buseAction\s*\(/.test(source) && !declared.has("actions.run:*")) {
|
|
702
|
+
findings.push({
|
|
703
|
+
rule: "scope-required-for-useAction",
|
|
704
|
+
label: "useAction() requires `actions.run:*` in manifest.requestedScopes",
|
|
705
|
+
line: 0,
|
|
706
|
+
snippet: "",
|
|
707
|
+
});
|
|
708
|
+
}
|
|
698
709
|
const lines = source.split(/\r?\n/);
|
|
699
710
|
let firedUsersWrite = false;
|
|
700
711
|
let firedUsersDelete = false;
|
package/dist/list.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// `<List>`, `<ListRow>`, `<ListHeader>`, `<EmptyState>` (web). Binds
|
|
2
2
|
// ./list-view.js to react-native-web; see ./primitives.js for why not the bare specifier.
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import { ReactNative } from "./text-scale.js";
|
|
5
5
|
import { Card } from "./card.js";
|
|
6
6
|
import { makeList } from "./list-view.js";
|
|
7
7
|
|
package/dist/loading.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// `<Spinner>` and `<Skeleton>` (web). Binds ./loading-view.js to
|
|
2
2
|
// react-native-web; see ./primitives.js for why not the bare specifier.
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import { ReactNative } from "./text-scale.js";
|
|
5
5
|
import { makeLoading } from "./loading-view.js";
|
|
6
6
|
|
|
7
7
|
export const { Spinner, Skeleton } = makeLoading(ReactNative, { useNativeDriver: false });
|
package/dist/markdown-input.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// ./markdown-input-view.js to react-native-web's primitives, and to the web
|
|
3
3
|
// `<RichText>` it renders its preview with.
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import { ReactNative } from "./text-scale.js";
|
|
6
6
|
import { makeMarkdownInput } from "./markdown-input-view.js";
|
|
7
7
|
import { RichText } from "./richtext.js";
|
|
8
8
|
import { Modal } from "./modal.js";
|
package/dist/modal.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// `<Modal>` (web). Binds ./modal-view.js to react-native-web and the web
|
|
2
2
|
// `<Overlay>`; see ./primitives.js for why not the bare specifier.
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import { ReactNative } from "./text-scale.js";
|
|
5
5
|
import { makeModal } from "./modal-view.js";
|
|
6
6
|
import { Overlay } from "./overlay.js";
|
|
7
7
|
|
package/dist/overlay.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// container at `position: fixed`, which is exactly what makes the overlay
|
|
9
9
|
// escape the widget's clipping ancestors.
|
|
10
10
|
|
|
11
|
-
import
|
|
11
|
+
import { ReactNative } from "./text-scale.js";
|
|
12
12
|
import { makeOverlay } from "./overlay-view.js";
|
|
13
13
|
import { WebModal } from "./web-modal.js";
|
|
14
14
|
|
package/dist/people-picker.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// `<PeoplePicker>` (web). Binds ./people-picker-view.js to the web `<Select>`
|
|
2
2
|
// and `<Avatar>`; see ./primitives.js for why not the bare specifier.
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import { ReactNative } from "./text-scale.js";
|
|
5
5
|
import { makePeoplePicker } from "./people-picker-view.js";
|
|
6
6
|
import { Select } from "./select.js";
|
|
7
7
|
import { Avatar } from "./detail-parts.js";
|
package/dist/popover.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// ./menu-view.js and ./tooltip-view.js to react-native-web and the web
|
|
3
3
|
// `<Overlay>`; see ./primitives.js for why not the bare specifier.
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import { ReactNative } from "./text-scale.js";
|
|
6
6
|
import { makePopover } from "./popover-view.js";
|
|
7
7
|
import { makeMenu } from "./menu-view.js";
|
|
8
8
|
import { makeTooltip } from "./tooltip-view.js";
|
package/dist/primitives.js
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
// installed dep (no stub), and runtime property access sidesteps the
|
|
40
40
|
// static re-export tracing. Behaviour is identical to the old
|
|
41
41
|
// `react-native` alias path on the web.
|
|
42
|
-
import
|
|
42
|
+
import { ReactNative } from "./text-scale.js";
|
|
43
43
|
import { makeTextInput } from "./text-input-view.js";
|
|
44
44
|
|
|
45
45
|
export const Text = ReactNative.Text;
|
package/dist/property-schema.js
CHANGED
|
@@ -28,6 +28,9 @@ const VALID_TYPES = new Set([
|
|
|
28
28
|
// and calls `useAgent(agentRef)`. Bare uuid string like `groupRef`/`pageRef`,
|
|
29
29
|
// so tenant-copy remaps it through the layout walk's merged id map.
|
|
30
30
|
"agentRef",
|
|
31
|
+
// sc-13843: `actionRef` picks one of the workspace's Actions, a bare
|
|
32
|
+
// Action UUID a widget passes to `useAction(actionRef)`. Remapped like agentRef.
|
|
33
|
+
"actionRef",
|
|
31
34
|
// REQ-WDG-PAGEREF: `pageRef` is a page picker that emits a bare AppPage
|
|
32
35
|
// UUID into the page JSON. Renders a page `<select>` in the Studio
|
|
33
36
|
// Properties Panel; a widget reads it and navigates via
|
|
@@ -160,6 +163,7 @@ function coerceLeaf(def, value, path, errors) {
|
|
|
160
163
|
case "pageRef":
|
|
161
164
|
case "sectionRef":
|
|
162
165
|
case "agentRef":
|
|
166
|
+
case "actionRef":
|
|
163
167
|
case "richText":
|
|
164
168
|
case "asset":
|
|
165
169
|
case "folderRef":
|
package/dist/radio-group.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// `<RadioGroup>` (web). Binds ./radio-group-view.js to react-native-web; see
|
|
2
2
|
// ./primitives.js for why not the bare specifier.
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import { ReactNative } from "./text-scale.js";
|
|
5
5
|
import { makeRadioGroup } from "./radio-group-view.js";
|
|
6
6
|
|
|
7
7
|
export const RadioGroup = makeRadioGroup({
|
package/dist/richtext.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// specifier for the rolldown optional-peer-dep reason spelled out at the top of
|
|
6
6
|
// ./primitives.js.
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import { ReactNative } from "./text-scale.js";
|
|
9
9
|
import { makeRichText } from "./richtext-view.js";
|
|
10
10
|
|
|
11
11
|
export const RichText = makeRichText(ReactNative);
|
package/dist/search-field.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// `<SearchField>` (web). Binds ./search-field-view.js to react-native-web and
|
|
2
2
|
// the web `<TextField>`; see ./primitives.js for why not the bare specifier.
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import { ReactNative } from "./text-scale.js";
|
|
5
5
|
import { makeSearchField } from "./search-field-view.js";
|
|
6
6
|
import { TextField } from "./text-field.js";
|
|
7
7
|
|
package/dist/select.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// `<Overlay>`, `<Button>`, `<Chip>`, `<SearchField>` and `<Spinner>`; see
|
|
3
3
|
// ./primitives.js for why not the bare specifier.
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import { ReactNative } from "./text-scale.js";
|
|
6
6
|
import { makeSelect } from "./select-view.js";
|
|
7
7
|
import { Overlay } from "./overlay.js";
|
|
8
8
|
import { Button } from "./button.js";
|
package/dist/sortable.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// `<SortableList>` and `<Board>` (web). Bind ./sortable-view.js to
|
|
2
2
|
// react-native-web; see ./primitives.js for why not the bare specifier.
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import { ReactNative } from "./text-scale.js";
|
|
5
5
|
import { List, ListRow } from "./list.js";
|
|
6
6
|
import { Card } from "./card.js";
|
|
7
7
|
import { Menu } from "./popover.js";
|
package/dist/switch.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// - it forwards only a label to its `<input>`, dropping the other ARIA props.
|
|
11
11
|
|
|
12
12
|
import React from "react";
|
|
13
|
-
import
|
|
13
|
+
import { ReactNative } from "./text-scale.js";
|
|
14
14
|
import { makeSwitch } from "./switch-view.js";
|
|
15
15
|
import { DISABLED_OPACITY } from "./form-control-tokens.js";
|
|
16
16
|
import { shareRef } from "./form-control-logic.js";
|
package/dist/tabs.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// `<Tabs>` (web). Binds ./tabs-view.js to react-native-web; see
|
|
2
2
|
// ./primitives.js for why not the bare specifier.
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import { ReactNative } from "./text-scale.js";
|
|
5
5
|
import { makeTabs } from "./tabs-view.js";
|
|
6
6
|
|
|
7
7
|
export const Tabs = makeTabs(ReactNative);
|
package/dist/text-field.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// `<TextField>` (web). Binds ./text-field-view.js to react-native-web; see
|
|
2
2
|
// ./primitives.js for why not the bare specifier.
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import { ReactNative } from "./text-scale.js";
|
|
5
5
|
import { makeTextField } from "./text-field-view.js";
|
|
6
6
|
import { TextInput } from "./primitives.js";
|
|
7
7
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// sc-12791: device font-scale arithmetic, React-free so node tests can load it.
|
|
2
|
+
|
|
3
|
+
// Unstyled text renders at 14px in both React Native and react-native-web.
|
|
4
|
+
export const DEFAULT_FONT_SIZE = 14;
|
|
5
|
+
|
|
6
|
+
// `em` is relative to an already-scaled parent, so it is left alone.
|
|
7
|
+
const ABSOLUTE_LENGTH = /\d(?:px|rem)$/;
|
|
8
|
+
|
|
9
|
+
function flattenStyle(style, into = {}) {
|
|
10
|
+
if (Array.isArray(style)) {
|
|
11
|
+
for (const entry of style) flattenStyle(entry, into);
|
|
12
|
+
} else if (style && typeof style === "object") {
|
|
13
|
+
Object.assign(into, style);
|
|
14
|
+
}
|
|
15
|
+
return into;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function scaleLength(value, factor) {
|
|
19
|
+
if (typeof value === "number") return value * factor;
|
|
20
|
+
if (typeof value === "string" && ABSOLUTE_LENGTH.test(value.trim())) {
|
|
21
|
+
return `calc(${value.trim()} * ${factor})`;
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The multiplier one text element gets under a preview scale, honouring the
|
|
28
|
+
* same per-element props React Native does.
|
|
29
|
+
*
|
|
30
|
+
* @param {number} scale The previewed device font scale.
|
|
31
|
+
* @param {{ allowFontScaling?: boolean, maxFontSizeMultiplier?: number }} props
|
|
32
|
+
* @returns {number}
|
|
33
|
+
*/
|
|
34
|
+
export function textScaleFactor(scale, props) {
|
|
35
|
+
if (!(scale > 1) || props?.allowFontScaling === false) return 1;
|
|
36
|
+
const cap = props?.maxFontSizeMultiplier;
|
|
37
|
+
return typeof cap === "number" && cap >= 1 ? Math.min(scale, cap) : scale;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* `style` with its font size and line height multiplied by `factor`.
|
|
42
|
+
*
|
|
43
|
+
* @param {any} style A React Native style (object, array, or falsy).
|
|
44
|
+
* @param {number} factor
|
|
45
|
+
* @param {{ inheritsSize?: boolean }} [options] `inheritsSize` marks text
|
|
46
|
+
* nested in already-scaled text: without its own size it inherits the
|
|
47
|
+
* parent's, which must not be multiplied twice.
|
|
48
|
+
* @returns {any} The original style when nothing changes.
|
|
49
|
+
*/
|
|
50
|
+
export function scaleTextStyle(style, factor, { inheritsSize = false } = {}) {
|
|
51
|
+
if (factor === 1) return style;
|
|
52
|
+
const flat = flattenStyle(style);
|
|
53
|
+
const override = {};
|
|
54
|
+
const fontSize = flat.fontSize ?? (inheritsSize ? undefined : DEFAULT_FONT_SIZE);
|
|
55
|
+
const scaledFontSize = scaleLength(fontSize, factor);
|
|
56
|
+
if (scaledFontSize !== undefined) override.fontSize = scaledFontSize;
|
|
57
|
+
const scaledLineHeight = scaleLength(flat.lineHeight, factor);
|
|
58
|
+
if (scaledLineHeight !== undefined) override.lineHeight = scaledLineHeight;
|
|
59
|
+
return Object.keys(override).length > 0 ? [style, override] : style;
|
|
60
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Previews a device font scale for every SDK `Text` / `TextInput` below it, as
|
|
3
|
+
* a phone's text-size setting would: font sizes and line heights grow, layout
|
|
4
|
+
* boxes do not. Web hosts only — native text already follows the device.
|
|
5
|
+
*/
|
|
6
|
+
export function TextScaleProvider(props: { scale?: number; children?: unknown }): unknown;
|
|
7
|
+
|
|
8
|
+
/** react-native-web's `Text`, following the nearest `TextScaleProvider`. */
|
|
9
|
+
export const Text: unknown;
|
|
10
|
+
/** react-native-web's `TextInput`, following the nearest `TextScaleProvider`. */
|
|
11
|
+
export const TextInput: unknown;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Web twin of ./bounded-text.native.js: previews a device text-size setting.
|
|
2
|
+
// Every SDK web module takes react-native-web from here (text-scale.test.js).
|
|
3
|
+
import React from "react";
|
|
4
|
+
import * as ReactNativeWeb from "react-native-web";
|
|
5
|
+
import { scaleTextStyle, textScaleFactor } from "./text-scale-style.js";
|
|
6
|
+
|
|
7
|
+
const TextScaleContext = React.createContext(1);
|
|
8
|
+
const ScaledTextAncestorContext = React.createContext(false);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Previews a device font scale for every SDK Text / TextInput below it.
|
|
12
|
+
* Web hosts only: native text already follows the device setting.
|
|
13
|
+
*
|
|
14
|
+
* @param {{ scale?: number, children?: React.ReactNode }} props
|
|
15
|
+
*/
|
|
16
|
+
export function TextScaleProvider({ scale = 1, children }) {
|
|
17
|
+
const value = Number.isFinite(scale) && scale > 0 ? scale : 1;
|
|
18
|
+
return React.createElement(TextScaleContext.Provider, { value }, children);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function withTextScale(Base, { displayName, nests = false }) {
|
|
22
|
+
const Scaled = React.forwardRef(function ScaledText(props, ref) {
|
|
23
|
+
const scale = React.useContext(TextScaleContext);
|
|
24
|
+
const insideScaledText = React.useContext(ScaledTextAncestorContext);
|
|
25
|
+
if (scale === 1) return React.createElement(Base, { ...props, ref });
|
|
26
|
+
const factor = textScaleFactor(scale, props);
|
|
27
|
+
const style = scaleTextStyle(props.style, factor, { inheritsSize: insideScaledText });
|
|
28
|
+
const element = React.createElement(Base, { ...props, ref, style });
|
|
29
|
+
// Children inherit this size even when this element opted out of scaling.
|
|
30
|
+
if (!nests || insideScaledText) return element;
|
|
31
|
+
return React.createElement(ScaledTextAncestorContext.Provider, { value: true }, element);
|
|
32
|
+
});
|
|
33
|
+
Scaled.displayName = displayName;
|
|
34
|
+
return Scaled;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const Text = withTextScale(ReactNativeWeb.Text, { displayName: "Text", nests: true });
|
|
38
|
+
export const TextInput = withTextScale(ReactNativeWeb.TextInput, { displayName: "TextInput" });
|
|
39
|
+
// Spread, not re-exported; see ./primitives.js for why.
|
|
40
|
+
export const ReactNative = { ...ReactNativeWeb, Text, TextInput };
|
package/dist/video.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// thumbnail; Safari draws nothing before playback without the fragment.
|
|
10
10
|
|
|
11
11
|
import React from "react";
|
|
12
|
-
import
|
|
12
|
+
import { ReactNative } from "./text-scale.js";
|
|
13
13
|
|
|
14
14
|
const FIRST_FRAME = "#t=0.001";
|
|
15
15
|
const FITS = { contain: "contain", cover: "cover" };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.262.0",
|
|
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",
|
|
@@ -29,6 +29,11 @@
|
|
|
29
29
|
"import": "./dist/host.js",
|
|
30
30
|
"default": "./dist/host.js"
|
|
31
31
|
},
|
|
32
|
+
"./text-scale": {
|
|
33
|
+
"types": "./dist/text-scale.d.ts",
|
|
34
|
+
"import": "./dist/text-scale.js",
|
|
35
|
+
"default": "./dist/text-scale.js"
|
|
36
|
+
},
|
|
32
37
|
"./contract": {
|
|
33
38
|
"types": "./dist/index.d.ts",
|
|
34
39
|
"require": "./dist/contract.cjs",
|