@colixsystems/widget-sdk 0.198.0 → 0.200.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 +96 -3
- package/dist/bundled-assets-fs.js +60 -0
- package/dist/bundled-assets.cjs +239 -0
- package/dist/bundled-assets.js +239 -0
- package/dist/cli.js +52 -11
- package/dist/contract.cjs +42 -3
- package/dist/contract.js +42 -3
- package/dist/devserver.js +68 -10
- package/dist/emoji-picker.js +2 -1
- package/dist/hooks.js +14 -0
- package/dist/host.d.ts +6 -0
- package/dist/host.js +1 -0
- package/dist/index.d.ts +70 -1
- package/dist/index.js +6 -0
- package/dist/index.native.js +6 -0
- package/dist/linter.cjs +28 -0
- package/dist/linter.js +28 -0
- package/dist/manifest.cjs +1 -0
- package/dist/manifest.js +1 -0
- package/dist/markdown-input.js +6 -1
- package/dist/primitives.js +2 -1
- package/dist/text-field.js +2 -1
- package/dist/text-input-view.js +27 -0
- package/dist/theme-background.cjs +26 -3
- package/dist/theme-background.js +20 -2
- package/dist/touch-input-font-floor.js +22 -0
- package/package.json +7 -1
- package/src/contract.js +42 -3
package/dist/linter.js
CHANGED
|
@@ -1081,6 +1081,33 @@ function _jsxOpenTagEnd(source, from) {
|
|
|
1081
1081
|
return source.length;
|
|
1082
1082
|
}
|
|
1083
1083
|
|
|
1084
|
+
// sc-10721 — a useBundledAsset literal must name a file the package ships.
|
|
1085
|
+
const BUNDLED_ASSET_CALL_RE =
|
|
1086
|
+
/\buseBundledAsset\s*\(\s*(?:"([^"\n]*)"|'([^'\n]*)'|`([^`]*)`)\s*[,)]/g;
|
|
1087
|
+
|
|
1088
|
+
function _bundledAssetRules(source, assetFiles) {
|
|
1089
|
+
if (!Array.isArray(assetFiles)) return [];
|
|
1090
|
+
const findings = [];
|
|
1091
|
+
// Comments blanked, strings kept: the file name IS a string literal.
|
|
1092
|
+
const code = _stripNonCode(source, { keepStrings: true });
|
|
1093
|
+
const sourceLines = source.split(/\r?\n/);
|
|
1094
|
+
for (const match of code.matchAll(BUNDLED_ASSET_CALL_RE)) {
|
|
1095
|
+
const name = match[1] || match[2] || match[3] || "";
|
|
1096
|
+
if (name.includes("${") || assetFiles.includes(name)) continue;
|
|
1097
|
+
const line = code.slice(0, match.index).split(/\r?\n/).length;
|
|
1098
|
+
findings.push({
|
|
1099
|
+
rule: "bundled-asset-missing",
|
|
1100
|
+
severity: "error",
|
|
1101
|
+
label:
|
|
1102
|
+
`useBundledAsset("${name}") names a file that is not in the package's ` +
|
|
1103
|
+
"assets/ directory. Add the file there or correct the name.",
|
|
1104
|
+
line,
|
|
1105
|
+
snippet: (sourceLines[line - 1] || "").trim().slice(0, 200),
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
return findings;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1084
1111
|
// sc-4649 / sc-4686 — payment-currency.
|
|
1085
1112
|
//
|
|
1086
1113
|
// A widget must not name a currency AT ALL. Each workspace charges its app users
|
|
@@ -2080,6 +2107,7 @@ export function lintSource(source, options) {
|
|
|
2080
2107
|
findings.push(..._translationApiRules(source));
|
|
2081
2108
|
findings.push(..._handBuiltPageUrlRules(source));
|
|
2082
2109
|
findings.push(..._lucideIconRules(source));
|
|
2110
|
+
findings.push(..._bundledAssetRules(source, options && options.assetFiles));
|
|
2083
2111
|
// sc-2353 / sc-5897 — widget source must be self-contained: a referenced
|
|
2084
2112
|
// `React` global or SDK export with no import throws at render.
|
|
2085
2113
|
findings.push(..._selfContainedReferenceRules(source));
|
package/dist/manifest.cjs
CHANGED
package/dist/manifest.js
CHANGED
package/dist/markdown-input.js
CHANGED
|
@@ -6,5 +6,10 @@ import * as ReactNative from "react-native-web";
|
|
|
6
6
|
import { makeMarkdownInput } from "./markdown-input-view.js";
|
|
7
7
|
import { RichText } from "./richtext.js";
|
|
8
8
|
import { Overlay } from "./overlay.js";
|
|
9
|
+
import { TextInput } from "./primitives.js";
|
|
9
10
|
|
|
10
|
-
export const MarkdownInput = makeMarkdownInput(
|
|
11
|
+
export const MarkdownInput = makeMarkdownInput(
|
|
12
|
+
{ ...ReactNative, TextInput },
|
|
13
|
+
RichText,
|
|
14
|
+
Overlay,
|
|
15
|
+
);
|
package/dist/primitives.js
CHANGED
|
@@ -40,13 +40,14 @@
|
|
|
40
40
|
// static re-export tracing. Behaviour is identical to the old
|
|
41
41
|
// `react-native` alias path on the web.
|
|
42
42
|
import * as ReactNative from "react-native-web";
|
|
43
|
+
import { makeTextInput } from "./text-input-view.js";
|
|
43
44
|
|
|
44
45
|
export const Text = ReactNative.Text;
|
|
45
46
|
export const View = ReactNative.View;
|
|
46
47
|
export const Pressable = ReactNative.Pressable;
|
|
47
48
|
export const Image = ReactNative.Image;
|
|
48
49
|
export const ScrollView = ReactNative.ScrollView;
|
|
49
|
-
export const TextInput = ReactNative.TextInput;
|
|
50
|
+
export const TextInput = makeTextInput(ReactNative.TextInput);
|
|
50
51
|
// Additional cross-platform primitives the AI agent + handwritten
|
|
51
52
|
// widgets commonly reach for. All work in both react-native-web and
|
|
52
53
|
// native react-native without per-platform code.
|
package/dist/text-field.js
CHANGED
|
@@ -3,5 +3,6 @@
|
|
|
3
3
|
|
|
4
4
|
import * as ReactNative from "react-native-web";
|
|
5
5
|
import { makeTextField } from "./text-field-view.js";
|
|
6
|
+
import { TextInput } from "./primitives.js";
|
|
6
7
|
|
|
7
|
-
export const TextField = makeTextField(ReactNative);
|
|
8
|
+
export const TextField = makeTextField({ ...ReactNative, TextInput });
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// The web `<TextInput>`: the host's input with the touch font floor. Native
|
|
2
|
+
// has no focus zoom, so ./primitives.native.js re-exports RN's input as is.
|
|
3
|
+
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { withTouchInputFontFloor } from "./touch-input-font-floor.js";
|
|
6
|
+
|
|
7
|
+
function hasCoarsePointer() {
|
|
8
|
+
return (
|
|
9
|
+
typeof window !== "undefined" &&
|
|
10
|
+
typeof window.matchMedia === "function" &&
|
|
11
|
+
window.matchMedia("(pointer: coarse)").matches
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {any} BaseTextInput react-native-web's `TextInput`.
|
|
17
|
+
* @returns a `TextInput` taking the same props and ref.
|
|
18
|
+
*/
|
|
19
|
+
export function makeTextInput(BaseTextInput) {
|
|
20
|
+
return React.forwardRef(function TextInput({ style, ...rest }, ref) {
|
|
21
|
+
return React.createElement(BaseTextInput, {
|
|
22
|
+
...rest,
|
|
23
|
+
ref,
|
|
24
|
+
style: withTouchInputFontFloor(style, hasCoarsePointer()),
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -68,8 +68,8 @@ function normaliseImageUrl(raw) {
|
|
|
68
68
|
if (typeof raw !== "string") return null;
|
|
69
69
|
const url = raw.trim();
|
|
70
70
|
if (!url) return null;
|
|
71
|
-
// A
|
|
72
|
-
if (
|
|
71
|
+
// A same-origin path; `/\host` and whitespace are foreign hosts to a browser.
|
|
72
|
+
if (/^\/(?![/\\])\S*$/.test(url)) return url;
|
|
73
73
|
return /^https?:\/\/\S+$/i.test(url) ? url : null;
|
|
74
74
|
}
|
|
75
75
|
|
|
@@ -135,4 +135,27 @@ function normaliseLoginDesign(raw) {
|
|
|
135
135
|
return configured ? design : null;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
|
|
138
|
+
/**
|
|
139
|
+
* sc-10845 — the sign-in card's logo: the author's own, else the app logo,
|
|
140
|
+
* else null. Hosts resolve `logoAssetId` into `logoUrlResolved` first. Not in
|
|
141
|
+
* `normaliseLoginDesign`: an app logo alone is not an authored design.
|
|
142
|
+
*
|
|
143
|
+
* @param {object|null|undefined} login - `themeConfig.login`.
|
|
144
|
+
* @param {string|null|undefined} appLogoUrl - the host's resolved app logo.
|
|
145
|
+
* @returns {string|null}
|
|
146
|
+
*/
|
|
147
|
+
function resolveLoginLogoUrl(login, appLogoUrl) {
|
|
148
|
+
const own =
|
|
149
|
+
login && typeof login === "object"
|
|
150
|
+
? normaliseImageUrl(login.logoUrlResolved) ||
|
|
151
|
+
normaliseImageUrl(login.logoUrl)
|
|
152
|
+
: null;
|
|
153
|
+
return own || normaliseImageUrl(appLogoUrl);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = {
|
|
157
|
+
LOGIN_TEXT_MAX_LENGTH,
|
|
158
|
+
normaliseThemeGradient,
|
|
159
|
+
normaliseLoginDesign,
|
|
160
|
+
resolveLoginLogoUrl,
|
|
161
|
+
};
|
package/dist/theme-background.js
CHANGED
|
@@ -60,8 +60,8 @@ function normaliseImageUrl(raw) {
|
|
|
60
60
|
if (typeof raw !== "string") return null;
|
|
61
61
|
const url = raw.trim();
|
|
62
62
|
if (!url) return null;
|
|
63
|
-
// A
|
|
64
|
-
if (
|
|
63
|
+
// A same-origin path; `/\host` and whitespace are foreign hosts to a browser.
|
|
64
|
+
if (/^\/(?![/\\])\S*$/.test(url)) return url;
|
|
65
65
|
return /^https?:\/\/\S+$/i.test(url) ? url : null;
|
|
66
66
|
}
|
|
67
67
|
|
|
@@ -126,3 +126,21 @@ export function normaliseLoginDesign(raw) {
|
|
|
126
126
|
const configured = Object.values(design).some((value) => value !== null);
|
|
127
127
|
return configured ? design : null;
|
|
128
128
|
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* sc-10845 — the sign-in card's logo: the author's own, else the app logo,
|
|
132
|
+
* else null. Hosts resolve `logoAssetId` into `logoUrlResolved` first. Not in
|
|
133
|
+
* `normaliseLoginDesign`: an app logo alone is not an authored design.
|
|
134
|
+
*
|
|
135
|
+
* @param {object|null|undefined} login - `themeConfig.login`.
|
|
136
|
+
* @param {string|null|undefined} appLogoUrl - the host's resolved app logo.
|
|
137
|
+
* @returns {string|null}
|
|
138
|
+
*/
|
|
139
|
+
export function resolveLoginLogoUrl(login, appLogoUrl) {
|
|
140
|
+
const own =
|
|
141
|
+
login && typeof login === "object"
|
|
142
|
+
? normaliseImageUrl(login.logoUrlResolved) ||
|
|
143
|
+
normaliseImageUrl(login.logoUrl)
|
|
144
|
+
: null;
|
|
145
|
+
return own || normaliseImageUrl(appLogoUrl);
|
|
146
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// iOS Safari zooms the page when a focused field's text is under 16px, and
|
|
2
|
+
// never zooms back out. Platform-free so it is testable without a DOM.
|
|
3
|
+
|
|
4
|
+
import { flattenStyle } from "./flatten-style.js";
|
|
5
|
+
|
|
6
|
+
export const MIN_TOUCH_INPUT_FONT_SIZE = 16;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `style` with its font size raised to the touch floor. An unset size is
|
|
10
|
+
* floored too: react-native-web's own input default is 14px.
|
|
11
|
+
*
|
|
12
|
+
* @param {object|Array|null|undefined|false} style
|
|
13
|
+
* @param {boolean} isTouch whether the primary pointer is a finger
|
|
14
|
+
* @returns {object|Array|null|undefined|false} `style` itself when unchanged.
|
|
15
|
+
*/
|
|
16
|
+
export function withTouchInputFontFloor(style, isTouch) {
|
|
17
|
+
if (!isTouch) return style;
|
|
18
|
+
const { fontSize } = flattenStyle(style);
|
|
19
|
+
if (typeof fontSize === "string") return style;
|
|
20
|
+
if (fontSize >= MIN_TOUCH_INPUT_FONT_SIZE) return style;
|
|
21
|
+
return [style, { fontSize: MIN_TOUCH_INPUT_FONT_SIZE }];
|
|
22
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.200.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",
|
|
@@ -35,6 +35,12 @@
|
|
|
35
35
|
"import": "./dist/contract.js",
|
|
36
36
|
"default": "./dist/contract.cjs"
|
|
37
37
|
},
|
|
38
|
+
"./bundled-assets": {
|
|
39
|
+
"types": "./dist/index.d.ts",
|
|
40
|
+
"require": "./dist/bundled-assets.cjs",
|
|
41
|
+
"import": "./dist/bundled-assets.js",
|
|
42
|
+
"default": "./dist/bundled-assets.cjs"
|
|
43
|
+
},
|
|
38
44
|
"./src/webbundle.js": "./src/webbundle.js"
|
|
39
45
|
},
|
|
40
46
|
"bin": {
|
package/src/contract.js
CHANGED
|
@@ -1171,6 +1171,20 @@ const HOOKS = [
|
|
|
1171
1171
|
requiredContextSlice: ["assets.list"],
|
|
1172
1172
|
scopes: null,
|
|
1173
1173
|
},
|
|
1174
|
+
{
|
|
1175
|
+
name: "useBundledAsset",
|
|
1176
|
+
signature: "useBundledAsset(fileName)",
|
|
1177
|
+
description:
|
|
1178
|
+
"Returns the URL of an image or sound file the widget package ships in " +
|
|
1179
|
+
"its assets/ directory, or null when the package has no file with that " +
|
|
1180
|
+
"name. Only an uploaded package can bundle files; a widget written in " +
|
|
1181
|
+
"the Studio has none.",
|
|
1182
|
+
returnShape: {
|
|
1183
|
+
"(returns)": "string | null",
|
|
1184
|
+
},
|
|
1185
|
+
requiredContextSlice: ["widget.assets"],
|
|
1186
|
+
scopes: null,
|
|
1187
|
+
},
|
|
1174
1188
|
{
|
|
1175
1189
|
name: "useFilestoreFiles",
|
|
1176
1190
|
signature: "useFilestoreFiles({ spaceType, recordId, folderId, q, type })",
|
|
@@ -2884,6 +2898,19 @@ const ACTION_SCRIPT_GLOBALS = [
|
|
|
2884
2898
|
// Mirrors action.service.js SCRIPT_MAX_BYTES.
|
|
2885
2899
|
const ACTION_SCRIPT_MAX_BYTES = 200 * 1024;
|
|
2886
2900
|
|
|
2901
|
+
// The one package size limit; the backend derives its upload caps from it.
|
|
2902
|
+
const PACKAGE_MAX_BYTES = 12 * 1024 * 1024;
|
|
2903
|
+
|
|
2904
|
+
const BUNDLED_ASSETS = {
|
|
2905
|
+
directory: "assets",
|
|
2906
|
+
maxFiles: 50,
|
|
2907
|
+
maxNameLength: 64,
|
|
2908
|
+
types: {
|
|
2909
|
+
image: { extensions: ["png", "jpg", "jpeg", "gif", "webp"] },
|
|
2910
|
+
audio: { extensions: ["mp3", "m4a", "wav"] },
|
|
2911
|
+
},
|
|
2912
|
+
};
|
|
2913
|
+
|
|
2887
2914
|
/**
|
|
2888
2915
|
* sc-4915 — the trigger set a manifest action declares, canonically ordered.
|
|
2889
2916
|
*
|
|
@@ -3067,9 +3094,15 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
3067
3094
|
},
|
|
3068
3095
|
widget: {
|
|
3069
3096
|
description:
|
|
3070
|
-
"Identity of the currently rendered widget instance ({ id, version })
|
|
3097
|
+
"Identity of the currently rendered widget instance ({ id, version }) " +
|
|
3098
|
+
"and the files its package bundles.",
|
|
3071
3099
|
required: true,
|
|
3072
|
-
fields: {
|
|
3100
|
+
fields: {
|
|
3101
|
+
id: "manifest.id",
|
|
3102
|
+
version: "manifest.version",
|
|
3103
|
+
assets:
|
|
3104
|
+
"{ [fileName]: string } — bundled file name → absolute URL; {} when the package bundles nothing",
|
|
3105
|
+
},
|
|
3073
3106
|
},
|
|
3074
3107
|
// REQ-LAY-08 — optional host layout hint backing useFill(). True when the
|
|
3075
3108
|
// host sized this widget to fill its layout slot's available height (a
|
|
@@ -5264,7 +5297,11 @@ const CONTRACT = deepFreeze({
|
|
|
5264
5297
|
// asset carrying `durationMs`. New `useHardwareBack(handler)` walks a
|
|
5265
5298
|
// widget's own pages on Android's back button (optional
|
|
5266
5299
|
// `ctx.navigation.onHardwareBack`; unsupported on the web).
|
|
5267
|
-
|
|
5300
|
+
// 1.159.0: additive (sc-10721) — a package may ship images and sounds in a
|
|
5301
|
+
// flat `assets/` directory (`bundledAssets`, within `packageMaxBytes`).
|
|
5302
|
+
// New `useBundledAsset(fileName)` resolves one through the new
|
|
5303
|
+
// `widget.assets` map; linter rule `bundled-asset-missing` (error).
|
|
5304
|
+
version: "1.159.0",
|
|
5268
5305
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
5269
5306
|
hooks: HOOKS,
|
|
5270
5307
|
primitives: PRIMITIVES,
|
|
@@ -5274,6 +5311,8 @@ const CONTRACT = deepFreeze({
|
|
|
5274
5311
|
actionTriggerTypes: ACTION_TRIGGER_TYPES,
|
|
5275
5312
|
actionScriptGlobals: ACTION_SCRIPT_GLOBALS,
|
|
5276
5313
|
actionScriptMaxBytes: ACTION_SCRIPT_MAX_BYTES,
|
|
5314
|
+
packageMaxBytes: PACKAGE_MAX_BYTES,
|
|
5315
|
+
bundledAssets: BUNDLED_ASSETS,
|
|
5277
5316
|
themeTokens: DEFAULT_THEME_TOKENS,
|
|
5278
5317
|
themeColorTokens: THEME_COLOR_TOKENS,
|
|
5279
5318
|
themeComponents: THEME_COMPONENTS,
|