@use-voltra/expo-plugin 2.3.1 → 2.4.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/build/cjs/index.js +7 -1
- package/build/cjs/index.js.map +1 -1
- package/build/cjs/utils/localePick.js +6 -43
- package/build/cjs/utils/localePick.js.map +1 -1
- package/build/cjs/widgetConfiguration.js +117 -0
- package/build/cjs/widgetConfiguration.js.map +1 -0
- package/build/esm/index.js +1 -0
- package/build/esm/index.js.map +1 -1
- package/build/esm/utils/localePick.js +3 -41
- package/build/esm/utils/localePick.js.map +1 -1
- package/build/esm/widgetConfiguration.js +110 -0
- package/build/esm/widgetConfiguration.js.map +1 -0
- package/build/types/index.d.ts +3 -1
- package/build/types/index.d.ts.map +1 -1
- package/build/types/types.d.ts +24 -0
- package/build/types/types.d.ts.map +1 -1
- package/build/types/utils/localePick.d.ts +3 -4
- package/build/types/utils/localePick.d.ts.map +1 -1
- package/build/types/widgetConfiguration.d.ts +31 -0
- package/build/types/widgetConfiguration.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/index.ts +11 -0
- package/src/types.ts +25 -0
- package/src/utils/localePick.ts +3 -51
- package/src/widgetConfiguration.node.test.ts +96 -0
- package/src/widgetConfiguration.ts +159 -0
package/build/cjs/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.widgetLabelEnglish = exports.isWidgetLocalizedMap = exports.prerenderWidgetState = exports.evaluateWidgetModuleExports = exports.evaluateWidgetModule = exports.createPrerenderWidgetModuleLoader = exports.resolveInstalledPackageVersion = exports.logger = exports.pickLocalizedValue = exports.normalizeLocaleTag = exports.resolveFontPaths = exports.addApplicationGroupsEntitlement = exports.validateWidgetLabel = exports.validateWidgetEntry = exports.validateInitialStatePath = exports.validateHomeScreenWidgetId = exports.normalizeWidgetEntryPath = exports.assertValidLocaleKey = exports.validateWidgetServerUpdate = exports.resolveWidgetServerUpdate = exports.validateServerUpdateRefresh = exports.resolveServerUpdateUrl = exports.resolveServerUpdateInterval = exports.isLocalHttpHost = exports.DYNAMIC_WIDGET_SERVER_UPDATE_INTERVAL_MINUTES = exports.getDynamicLiveActivityAttributesType = exports.MAX_IMAGE_SIZE_BYTES = void 0;
|
|
3
|
+
exports.widgetParameterTitleKey = exports.widgetParameterOptionKey = exports.validateWidgetConfigurationCopy = exports.collectWidgetConfigurationStrings = exports.collectLabelLocaleKeys = exports.widgetLabelEnglish = exports.isWidgetLocalizedMap = exports.prerenderWidgetState = exports.evaluateWidgetModuleExports = exports.evaluateWidgetModule = exports.createPrerenderWidgetModuleLoader = exports.resolveInstalledPackageVersion = exports.logger = exports.pickLocalizedValue = exports.normalizeLocaleTag = exports.resolveFontPaths = exports.addApplicationGroupsEntitlement = exports.validateWidgetLabel = exports.validateWidgetEntry = exports.validateInitialStatePath = exports.validateHomeScreenWidgetId = exports.normalizeWidgetEntryPath = exports.assertValidLocaleKey = exports.validateWidgetServerUpdate = exports.resolveWidgetServerUpdate = exports.validateServerUpdateRefresh = exports.resolveServerUpdateUrl = exports.resolveServerUpdateInterval = exports.isLocalHttpHost = exports.DYNAMIC_WIDGET_SERVER_UPDATE_INTERVAL_MINUTES = exports.getDynamicLiveActivityAttributesType = exports.MAX_IMAGE_SIZE_BYTES = void 0;
|
|
4
4
|
var constants_1 = require("./constants");
|
|
5
5
|
Object.defineProperty(exports, "MAX_IMAGE_SIZE_BYTES", { enumerable: true, get: function () { return constants_1.MAX_IMAGE_SIZE_BYTES; } });
|
|
6
6
|
var dynamic_live_activity_1 = require("./dynamic-live-activity");
|
|
@@ -40,4 +40,10 @@ Object.defineProperty(exports, "prerenderWidgetState", { enumerable: true, get:
|
|
|
40
40
|
var widgetLabel_1 = require("./utils/widgetLabel");
|
|
41
41
|
Object.defineProperty(exports, "isWidgetLocalizedMap", { enumerable: true, get: function () { return widgetLabel_1.isWidgetLocalizedMap; } });
|
|
42
42
|
Object.defineProperty(exports, "widgetLabelEnglish", { enumerable: true, get: function () { return widgetLabel_1.widgetLabelEnglish; } });
|
|
43
|
+
var widgetConfiguration_1 = require("./widgetConfiguration");
|
|
44
|
+
Object.defineProperty(exports, "collectLabelLocaleKeys", { enumerable: true, get: function () { return widgetConfiguration_1.collectLabelLocaleKeys; } });
|
|
45
|
+
Object.defineProperty(exports, "collectWidgetConfigurationStrings", { enumerable: true, get: function () { return widgetConfiguration_1.collectWidgetConfigurationStrings; } });
|
|
46
|
+
Object.defineProperty(exports, "validateWidgetConfigurationCopy", { enumerable: true, get: function () { return widgetConfiguration_1.validateWidgetConfigurationCopy; } });
|
|
47
|
+
Object.defineProperty(exports, "widgetParameterOptionKey", { enumerable: true, get: function () { return widgetConfiguration_1.widgetParameterOptionKey; } });
|
|
48
|
+
Object.defineProperty(exports, "widgetParameterTitleKey", { enumerable: true, get: function () { return widgetConfiguration_1.widgetParameterTitleKey; } });
|
|
43
49
|
//# sourceMappingURL=index.js.map
|
package/build/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,yCAAkD;AAAzC,iHAAA,oBAAoB,OAAA;AAC7B,iEAA8E;AAArE,6IAAA,oCAAoC,OAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,yCAAkD;AAAzC,iHAAA,oBAAoB,OAAA;AAC7B,iEAA8E;AAArE,6IAAA,oCAAoC,OAAA;AAiB7C,+CAMuB;AALrB,6IAAA,6CAA6C,OAAA;AAC7C,+GAAA,eAAe,OAAA;AACf,2HAAA,2BAA2B,OAAA;AAC3B,sHAAA,sBAAsB,OAAA;AACtB,2HAAA,2BAA2B,OAAA;AAO7B,2DAA4F;AAAnF,+HAAA,yBAAyB,OAAA;AAAE,gIAAA,0BAA0B,OAAA;AAC9D,2CAOqB;AANnB,kHAAA,oBAAoB,OAAA;AACpB,sHAAA,wBAAwB,OAAA;AACxB,wHAAA,0BAA0B,OAAA;AAC1B,sHAAA,wBAAwB,OAAA;AACxB,iHAAA,mBAAmB,OAAA;AACnB,iHAAA,mBAAmB,OAAA;AAErB,qDAAsE;AAA7D,+HAAA,+BAA+B,OAAA;AACxC,uCAAgD;AAAvC,yGAAA,gBAAgB,OAAA;AACzB,iDAA2E;AAAlE,gHAAA,kBAAkB,OAAA;AAAE,gHAAA,kBAAkB,OAAA;AAC/C,yCAAuC;AAA9B,gGAAA,MAAM,OAAA;AACf,yDAAuE;AAA9D,gIAAA,8BAA8B,OAAA;AAOvC,+CAK0B;AAJxB,8HAAA,iCAAiC,OAAA;AACjC,iHAAA,oBAAoB,OAAA;AACpB,wHAAA,2BAA2B,OAAA;AAC3B,iHAAA,oBAAoB,OAAA;AAGtB,mDAA8E;AAArE,mHAAA,oBAAoB,OAAA;AAAE,iHAAA,kBAAkB,OAAA;AAEjD,6DAM8B;AAL5B,6HAAA,sBAAsB,OAAA;AACtB,wIAAA,iCAAiC,OAAA;AACjC,sIAAA,+BAA+B,OAAA;AAC/B,+HAAA,wBAAwB,OAAA;AACxB,8HAAA,uBAAuB,OAAA"}
|
|
@@ -1,48 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.normalizeLocaleTag =
|
|
4
|
-
exports.pickLocalizedValue = pickLocalizedValue;
|
|
3
|
+
exports.pickLocalizedValue = exports.normalizeLocaleTag = void 0;
|
|
5
4
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Locale picking now lives in `@use-voltra/core` so widget code can use the same fallback rules
|
|
6
|
+
* at render time (`resolveLocale`). Re-exported here so existing imports keep working.
|
|
8
7
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
function pickLocalizedValue(perLocale, preferredLanguages) {
|
|
13
|
-
const entries = Object.entries(perLocale).filter(([, v]) => typeof v === 'string' && v.length > 0);
|
|
14
|
-
if (entries.length === 0) {
|
|
15
|
-
return undefined;
|
|
16
|
-
}
|
|
17
|
-
const byNorm = new Map();
|
|
18
|
-
for (const [k, v] of entries) {
|
|
19
|
-
byNorm.set(normalizeLocaleTag(k), v);
|
|
20
|
-
}
|
|
21
|
-
const DEFAULT_KEY = '__default';
|
|
22
|
-
for (const pref of preferredLanguages) {
|
|
23
|
-
const n = normalizeLocaleTag(pref);
|
|
24
|
-
const direct = byNorm.get(n);
|
|
25
|
-
if (direct !== undefined) {
|
|
26
|
-
return direct;
|
|
27
|
-
}
|
|
28
|
-
const lang = n.split('-')[0] ?? n;
|
|
29
|
-
for (const [key, val] of entries) {
|
|
30
|
-
const kn = normalizeLocaleTag(key);
|
|
31
|
-
const keyLang = kn.split('-')[0] ?? kn;
|
|
32
|
-
if (keyLang === lang) {
|
|
33
|
-
return val;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
const en = byNorm.get('en') ?? entries.find(([k]) => normalizeLocaleTag(k) === 'en')?.[1];
|
|
38
|
-
if (en !== undefined) {
|
|
39
|
-
return en;
|
|
40
|
-
}
|
|
41
|
-
const defaultVal = byNorm.get(DEFAULT_KEY) ?? entries.find(([k]) => k === DEFAULT_KEY)?.[1];
|
|
42
|
-
if (defaultVal !== undefined) {
|
|
43
|
-
return defaultVal;
|
|
44
|
-
}
|
|
45
|
-
const sorted = [...entries].sort(([a], [b]) => a.localeCompare(b));
|
|
46
|
-
return sorted[0]?.[1];
|
|
47
|
-
}
|
|
8
|
+
var locale_1 = require("@use-voltra/core/locale");
|
|
9
|
+
Object.defineProperty(exports, "normalizeLocaleTag", { enumerable: true, get: function () { return locale_1.normalizeLocaleTag; } });
|
|
10
|
+
Object.defineProperty(exports, "pickLocalizedValue", { enumerable: true, get: function () { return locale_1.pickLocalizedValue; } });
|
|
48
11
|
//# sourceMappingURL=localePick.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"localePick.js","sourceRoot":"","sources":["../../../src/utils/localePick.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"localePick.js","sourceRoot":"","sources":["../../../src/utils/localePick.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACH,kDAAgF;AAAvE,4GAAA,kBAAkB,OAAA;AAAE,4GAAA,kBAAkB,OAAA"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.widgetParameterTitleKey = widgetParameterTitleKey;
|
|
4
|
+
exports.widgetParameterOptionKey = widgetParameterOptionKey;
|
|
5
|
+
exports.collectWidgetConfigurationStrings = collectWidgetConfigurationStrings;
|
|
6
|
+
exports.collectLabelLocaleKeys = collectLabelLocaleKeys;
|
|
7
|
+
exports.validateWidgetConfigurationCopy = validateWidgetConfigurationCopy;
|
|
8
|
+
const widgetLabel_1 = require("./utils/widgetLabel");
|
|
9
|
+
const validation_1 = require("./validation");
|
|
10
|
+
/** Swift property / Kotlin-safe identifier, also the key under `env.configuration`. */
|
|
11
|
+
const PARAMETER_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
12
|
+
const identity = (value) => value;
|
|
13
|
+
function widgetParameterTitleKey(widgetId, parameterName, part = identity) {
|
|
14
|
+
return `voltra_widget_${part(widgetId)}_param_${part(parameterName)}_title`;
|
|
15
|
+
}
|
|
16
|
+
function widgetParameterOptionKey(widgetId, parameterName, optionValue, part = identity) {
|
|
17
|
+
return `voltra_widget_${part(widgetId)}_param_${part(parameterName)}_option_${part(optionValue)}`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Every configuration-sheet string a widget declares, in declaration order. `part` sanitises each
|
|
21
|
+
* key segment (Android resource names allow only `[a-z0-9_]`).
|
|
22
|
+
*/
|
|
23
|
+
function collectWidgetConfigurationStrings(widget, part = identity) {
|
|
24
|
+
const strings = [];
|
|
25
|
+
for (const parameter of widget.appIntent?.parameters ?? []) {
|
|
26
|
+
if (parameter.title !== undefined) {
|
|
27
|
+
strings.push({ key: widgetParameterTitleKey(widget.id, parameter.name, part), label: parameter.title });
|
|
28
|
+
}
|
|
29
|
+
for (const option of parameter.options ?? []) {
|
|
30
|
+
strings.push({
|
|
31
|
+
key: widgetParameterOptionKey(widget.id, parameter.name, option.value, part),
|
|
32
|
+
label: option.title,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return strings;
|
|
37
|
+
}
|
|
38
|
+
/** Locale keys used by any locale map among `labels`, `__default` excluded. */
|
|
39
|
+
function collectLabelLocaleKeys(labels) {
|
|
40
|
+
const keys = new Set();
|
|
41
|
+
for (const label of labels) {
|
|
42
|
+
if (label === undefined || !(0, widgetLabel_1.isWidgetLocalizedMap)(label)) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
for (const [locale, value] of Object.entries(label)) {
|
|
46
|
+
if (locale !== '__default' && typeof value === 'string' && value.trim()) {
|
|
47
|
+
keys.add(locale);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return [...keys];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Validates `appIntent.parameters[]` (titles as plain strings or locale maps, and static
|
|
55
|
+
* `options`).
|
|
56
|
+
*/
|
|
57
|
+
function validateWidgetConfigurationCopy(widget, options) {
|
|
58
|
+
const widgetId = widget.id;
|
|
59
|
+
const appIntent = widget.appIntent;
|
|
60
|
+
if (appIntent === undefined) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (typeof appIntent !== 'object' || appIntent === null || !Array.isArray(appIntent.parameters)) {
|
|
64
|
+
throw new Error(`Widget '${widgetId}': appIntent.parameters must be an array`);
|
|
65
|
+
}
|
|
66
|
+
const names = new Set();
|
|
67
|
+
for (const [index, parameter] of appIntent.parameters.entries()) {
|
|
68
|
+
const context = `appIntent.parameters[${index}]`;
|
|
69
|
+
if (typeof parameter !== 'object' || parameter === null) {
|
|
70
|
+
throw new Error(`Widget '${widgetId}': ${context} must be an object`);
|
|
71
|
+
}
|
|
72
|
+
const { name, title, default: defaultValue, options: parameterOptions } = parameter;
|
|
73
|
+
if (typeof name !== 'string' || !name) {
|
|
74
|
+
throw new Error(`Widget '${widgetId}': ${context}.name must be a non-empty string`);
|
|
75
|
+
}
|
|
76
|
+
if ((options.requireIdentifierName ?? true) && !PARAMETER_NAME_PATTERN.test(name)) {
|
|
77
|
+
throw new Error(`Widget '${widgetId}': ${context}.name must start with a letter or underscore and contain only letters, digits and underscores`);
|
|
78
|
+
}
|
|
79
|
+
if (names.has(name)) {
|
|
80
|
+
throw new Error(`Widget '${widgetId}': appIntent parameter '${name}' is declared more than once`);
|
|
81
|
+
}
|
|
82
|
+
names.add(name);
|
|
83
|
+
if (title === undefined) {
|
|
84
|
+
if (options.requireParameterTitle) {
|
|
85
|
+
throw new Error(`Widget '${widgetId}': appIntent parameter '${name}' needs a title`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
(0, validation_1.validateWidgetLabel)(title, widgetId, `appIntent parameter '${name}' title`);
|
|
90
|
+
}
|
|
91
|
+
if (defaultValue !== undefined && typeof defaultValue !== 'string') {
|
|
92
|
+
throw new Error(`Widget '${widgetId}': appIntent parameter '${name}' default must be a string`);
|
|
93
|
+
}
|
|
94
|
+
if (parameterOptions === undefined) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (!Array.isArray(parameterOptions) || parameterOptions.length === 0) {
|
|
98
|
+
throw new Error(`Widget '${widgetId}': appIntent parameter '${name}' options must be a non-empty array`);
|
|
99
|
+
}
|
|
100
|
+
const values = new Set();
|
|
101
|
+
for (const option of parameterOptions) {
|
|
102
|
+
const value = option?.value;
|
|
103
|
+
if (typeof value !== 'string' || !value) {
|
|
104
|
+
throw new Error(`Widget '${widgetId}': every option of appIntent parameter '${name}' needs a string value`);
|
|
105
|
+
}
|
|
106
|
+
if (values.has(value)) {
|
|
107
|
+
throw new Error(`Widget '${widgetId}': appIntent parameter '${name}' lists option '${value}' twice`);
|
|
108
|
+
}
|
|
109
|
+
values.add(value);
|
|
110
|
+
(0, validation_1.validateWidgetLabel)(option.title, widgetId, `appIntent parameter '${name}' option '${value}' title`);
|
|
111
|
+
}
|
|
112
|
+
if (defaultValue !== undefined && !values.has(defaultValue)) {
|
|
113
|
+
throw new Error(`Widget '${widgetId}': appIntent parameter '${name}' default '${defaultValue}' is not one of its options`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=widgetConfiguration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widgetConfiguration.js","sourceRoot":"","sources":["../../src/widgetConfiguration.ts"],"names":[],"mappings":"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"}
|
package/build/esm/index.js
CHANGED
|
@@ -10,4 +10,5 @@ export { logger } from './utils/logger';
|
|
|
10
10
|
export { resolveInstalledPackageVersion } from './utils/packageVersion';
|
|
11
11
|
export { createPrerenderWidgetModuleLoader, evaluateWidgetModule, evaluateWidgetModuleExports, prerenderWidgetState, } from './utils/prerender';
|
|
12
12
|
export { isWidgetLocalizedMap, widgetLabelEnglish } from './utils/widgetLabel';
|
|
13
|
+
export { collectLabelLocaleKeys, collectWidgetConfigurationStrings, validateWidgetConfigurationCopy, widgetParameterOptionKey, widgetParameterTitleKey, } from './widgetConfiguration';
|
|
13
14
|
//# sourceMappingURL=index.js.map
|
package/build/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,EAAE,oCAAoC,EAAE,MAAM,yBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,EAAE,oCAAoC,EAAE,MAAM,yBAAyB,CAAA;AAiB9E,OAAO,EACL,6CAA6C,EAC7C,eAAe,EACf,2BAA2B,EAC3B,sBAAsB,EACtB,2BAA2B,GAC5B,MAAM,gBAAgB,CAAA;AAMvB,OAAO,EAAE,yBAAyB,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAA;AAC5F,OAAO,EACL,oBAAoB,EACpB,wBAAwB,EACxB,0BAA0B,EAC1B,wBAAwB,EACxB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,+BAA+B,EAAE,MAAM,sBAAsB,CAAA;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAChD,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAC3E,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AACvC,OAAO,EAAE,8BAA8B,EAAE,MAAM,wBAAwB,CAAA;AAOvE,OAAO,EACL,iCAAiC,EACjC,oBAAoB,EACpB,2BAA2B,EAC3B,oBAAoB,GACrB,MAAM,mBAAmB,CAAA;AAE1B,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAE9E,OAAO,EACL,sBAAsB,EACtB,iCAAiC,EACjC,+BAA+B,EAC/B,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,uBAAuB,CAAA"}
|
|
@@ -1,44 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Locale picking now lives in `@use-voltra/core` so widget code can use the same fallback rules
|
|
3
|
+
* at render time (`resolveLocale`). Re-exported here so existing imports keep working.
|
|
4
4
|
*/
|
|
5
|
-
export
|
|
6
|
-
return tag.trim().toLowerCase().replace(/_/g, '-');
|
|
7
|
-
}
|
|
8
|
-
export function pickLocalizedValue(perLocale, preferredLanguages) {
|
|
9
|
-
const entries = Object.entries(perLocale).filter(([, v]) => typeof v === 'string' && v.length > 0);
|
|
10
|
-
if (entries.length === 0) {
|
|
11
|
-
return undefined;
|
|
12
|
-
}
|
|
13
|
-
const byNorm = new Map();
|
|
14
|
-
for (const [k, v] of entries) {
|
|
15
|
-
byNorm.set(normalizeLocaleTag(k), v);
|
|
16
|
-
}
|
|
17
|
-
const DEFAULT_KEY = '__default';
|
|
18
|
-
for (const pref of preferredLanguages) {
|
|
19
|
-
const n = normalizeLocaleTag(pref);
|
|
20
|
-
const direct = byNorm.get(n);
|
|
21
|
-
if (direct !== undefined) {
|
|
22
|
-
return direct;
|
|
23
|
-
}
|
|
24
|
-
const lang = n.split('-')[0] ?? n;
|
|
25
|
-
for (const [key, val] of entries) {
|
|
26
|
-
const kn = normalizeLocaleTag(key);
|
|
27
|
-
const keyLang = kn.split('-')[0] ?? kn;
|
|
28
|
-
if (keyLang === lang) {
|
|
29
|
-
return val;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
const en = byNorm.get('en') ?? entries.find(([k]) => normalizeLocaleTag(k) === 'en')?.[1];
|
|
34
|
-
if (en !== undefined) {
|
|
35
|
-
return en;
|
|
36
|
-
}
|
|
37
|
-
const defaultVal = byNorm.get(DEFAULT_KEY) ?? entries.find(([k]) => k === DEFAULT_KEY)?.[1];
|
|
38
|
-
if (defaultVal !== undefined) {
|
|
39
|
-
return defaultVal;
|
|
40
|
-
}
|
|
41
|
-
const sorted = [...entries].sort(([a], [b]) => a.localeCompare(b));
|
|
42
|
-
return sorted[0]?.[1];
|
|
43
|
-
}
|
|
5
|
+
export { normalizeLocaleTag, pickLocalizedValue } from '@use-voltra/core/locale';
|
|
44
6
|
//# sourceMappingURL=localePick.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"localePick.js","sourceRoot":"","sources":["../../../src/utils/localePick.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,
|
|
1
|
+
{"version":3,"file":"localePick.js","sourceRoot":"","sources":["../../../src/utils/localePick.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { isWidgetLocalizedMap } from './utils/widgetLabel';
|
|
2
|
+
import { validateWidgetLabel } from './validation';
|
|
3
|
+
/** Swift property / Kotlin-safe identifier, also the key under `env.configuration`. */
|
|
4
|
+
const PARAMETER_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
5
|
+
const identity = (value) => value;
|
|
6
|
+
export function widgetParameterTitleKey(widgetId, parameterName, part = identity) {
|
|
7
|
+
return `voltra_widget_${part(widgetId)}_param_${part(parameterName)}_title`;
|
|
8
|
+
}
|
|
9
|
+
export function widgetParameterOptionKey(widgetId, parameterName, optionValue, part = identity) {
|
|
10
|
+
return `voltra_widget_${part(widgetId)}_param_${part(parameterName)}_option_${part(optionValue)}`;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Every configuration-sheet string a widget declares, in declaration order. `part` sanitises each
|
|
14
|
+
* key segment (Android resource names allow only `[a-z0-9_]`).
|
|
15
|
+
*/
|
|
16
|
+
export function collectWidgetConfigurationStrings(widget, part = identity) {
|
|
17
|
+
const strings = [];
|
|
18
|
+
for (const parameter of widget.appIntent?.parameters ?? []) {
|
|
19
|
+
if (parameter.title !== undefined) {
|
|
20
|
+
strings.push({ key: widgetParameterTitleKey(widget.id, parameter.name, part), label: parameter.title });
|
|
21
|
+
}
|
|
22
|
+
for (const option of parameter.options ?? []) {
|
|
23
|
+
strings.push({
|
|
24
|
+
key: widgetParameterOptionKey(widget.id, parameter.name, option.value, part),
|
|
25
|
+
label: option.title,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return strings;
|
|
30
|
+
}
|
|
31
|
+
/** Locale keys used by any locale map among `labels`, `__default` excluded. */
|
|
32
|
+
export function collectLabelLocaleKeys(labels) {
|
|
33
|
+
const keys = new Set();
|
|
34
|
+
for (const label of labels) {
|
|
35
|
+
if (label === undefined || !isWidgetLocalizedMap(label)) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
for (const [locale, value] of Object.entries(label)) {
|
|
39
|
+
if (locale !== '__default' && typeof value === 'string' && value.trim()) {
|
|
40
|
+
keys.add(locale);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return [...keys];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Validates `appIntent.parameters[]` (titles as plain strings or locale maps, and static
|
|
48
|
+
* `options`).
|
|
49
|
+
*/
|
|
50
|
+
export function validateWidgetConfigurationCopy(widget, options) {
|
|
51
|
+
const widgetId = widget.id;
|
|
52
|
+
const appIntent = widget.appIntent;
|
|
53
|
+
if (appIntent === undefined) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (typeof appIntent !== 'object' || appIntent === null || !Array.isArray(appIntent.parameters)) {
|
|
57
|
+
throw new Error(`Widget '${widgetId}': appIntent.parameters must be an array`);
|
|
58
|
+
}
|
|
59
|
+
const names = new Set();
|
|
60
|
+
for (const [index, parameter] of appIntent.parameters.entries()) {
|
|
61
|
+
const context = `appIntent.parameters[${index}]`;
|
|
62
|
+
if (typeof parameter !== 'object' || parameter === null) {
|
|
63
|
+
throw new Error(`Widget '${widgetId}': ${context} must be an object`);
|
|
64
|
+
}
|
|
65
|
+
const { name, title, default: defaultValue, options: parameterOptions } = parameter;
|
|
66
|
+
if (typeof name !== 'string' || !name) {
|
|
67
|
+
throw new Error(`Widget '${widgetId}': ${context}.name must be a non-empty string`);
|
|
68
|
+
}
|
|
69
|
+
if ((options.requireIdentifierName ?? true) && !PARAMETER_NAME_PATTERN.test(name)) {
|
|
70
|
+
throw new Error(`Widget '${widgetId}': ${context}.name must start with a letter or underscore and contain only letters, digits and underscores`);
|
|
71
|
+
}
|
|
72
|
+
if (names.has(name)) {
|
|
73
|
+
throw new Error(`Widget '${widgetId}': appIntent parameter '${name}' is declared more than once`);
|
|
74
|
+
}
|
|
75
|
+
names.add(name);
|
|
76
|
+
if (title === undefined) {
|
|
77
|
+
if (options.requireParameterTitle) {
|
|
78
|
+
throw new Error(`Widget '${widgetId}': appIntent parameter '${name}' needs a title`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
validateWidgetLabel(title, widgetId, `appIntent parameter '${name}' title`);
|
|
83
|
+
}
|
|
84
|
+
if (defaultValue !== undefined && typeof defaultValue !== 'string') {
|
|
85
|
+
throw new Error(`Widget '${widgetId}': appIntent parameter '${name}' default must be a string`);
|
|
86
|
+
}
|
|
87
|
+
if (parameterOptions === undefined) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (!Array.isArray(parameterOptions) || parameterOptions.length === 0) {
|
|
91
|
+
throw new Error(`Widget '${widgetId}': appIntent parameter '${name}' options must be a non-empty array`);
|
|
92
|
+
}
|
|
93
|
+
const values = new Set();
|
|
94
|
+
for (const option of parameterOptions) {
|
|
95
|
+
const value = option?.value;
|
|
96
|
+
if (typeof value !== 'string' || !value) {
|
|
97
|
+
throw new Error(`Widget '${widgetId}': every option of appIntent parameter '${name}' needs a string value`);
|
|
98
|
+
}
|
|
99
|
+
if (values.has(value)) {
|
|
100
|
+
throw new Error(`Widget '${widgetId}': appIntent parameter '${name}' lists option '${value}' twice`);
|
|
101
|
+
}
|
|
102
|
+
values.add(value);
|
|
103
|
+
validateWidgetLabel(option.title, widgetId, `appIntent parameter '${name}' option '${value}' title`);
|
|
104
|
+
}
|
|
105
|
+
if (defaultValue !== undefined && !values.has(defaultValue)) {
|
|
106
|
+
throw new Error(`Widget '${widgetId}': appIntent parameter '${name}' default '${defaultValue}' is not one of its options`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=widgetConfiguration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widgetConfiguration.js","sourceRoot":"","sources":["../../src/widgetConfiguration.ts"],"names":[],"mappings":"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"}
|
package/build/types/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { MAX_IMAGE_SIZE_BYTES } from './constants';
|
|
2
2
|
export { getDynamicLiveActivityAttributesType } from './dynamic-live-activity';
|
|
3
|
-
export type { DynamicWidgetEntryConfig, DynamicLiveActivityEntryConfig, DynamicLiveActivityManifest, DynamicLiveActivityManifestDefinition, DynamicWidgetManifest, DynamicWidgetManifestWidget, DynamicWidgetPlatform, WidgetInitialStatePath, WidgetLabel, WidgetLocalizedCopy, } from './types';
|
|
3
|
+
export type { DynamicWidgetEntryConfig, DynamicLiveActivityEntryConfig, DynamicLiveActivityManifest, DynamicLiveActivityManifestDefinition, DynamicWidgetManifest, DynamicWidgetManifestWidget, DynamicWidgetPlatform, WidgetInitialStatePath, WidgetConfigurationCopySource, WidgetConfigurationOption, WidgetConfigurationParameter, WidgetLabel, WidgetLocalizedCopy, } from './types';
|
|
4
4
|
export type { ServerUpdateIntervalResolution, ServerUpdateUrlResolution } from './serverUpdate';
|
|
5
5
|
export { DYNAMIC_WIDGET_SERVER_UPDATE_INTERVAL_MINUTES, isLocalHttpHost, resolveServerUpdateInterval, resolveServerUpdateUrl, validateServerUpdateRefresh, } from './serverUpdate';
|
|
6
6
|
export type { ResolvedWidgetServerUpdateConfig, WidgetServerUpdateConfig, WidgetServerUpdateRules, } from './widgetServerUpdate';
|
|
@@ -15,4 +15,6 @@ export type { PrerenderableWidget, PrerenderedWidgetStates, WidgetModuleEvaluati
|
|
|
15
15
|
export { createPrerenderWidgetModuleLoader, evaluateWidgetModule, evaluateWidgetModuleExports, prerenderWidgetState, } from './utils/prerender';
|
|
16
16
|
export type { WidgetModuleLoader, WidgetModulePlatform } from '@use-voltra/compiler';
|
|
17
17
|
export { isWidgetLocalizedMap, widgetLabelEnglish } from './utils/widgetLabel';
|
|
18
|
+
export type { WidgetConfigurationString } from './widgetConfiguration';
|
|
19
|
+
export { collectLabelLocaleKeys, collectWidgetConfigurationStrings, validateWidgetConfigurationCopy, widgetParameterOptionKey, widgetParameterTitleKey, } from './widgetConfiguration';
|
|
18
20
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,EAAE,oCAAoC,EAAE,MAAM,yBAAyB,CAAA;AAC9E,YAAY,EACV,wBAAwB,EACxB,8BAA8B,EAC9B,2BAA2B,EAC3B,qCAAqC,EACrC,qBAAqB,EACrB,2BAA2B,EAC3B,qBAAqB,EACrB,sBAAsB,EACtB,WAAW,EACX,mBAAmB,GACpB,MAAM,SAAS,CAAA;AAChB,YAAY,EAAE,8BAA8B,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAA;AAC/F,OAAO,EACL,6CAA6C,EAC7C,eAAe,EACf,2BAA2B,EAC3B,sBAAsB,EACtB,2BAA2B,GAC5B,MAAM,gBAAgB,CAAA;AACvB,YAAY,EACV,gCAAgC,EAChC,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,yBAAyB,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAA;AAC5F,OAAO,EACL,oBAAoB,EACpB,wBAAwB,EACxB,0BAA0B,EAC1B,wBAAwB,EACxB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,+BAA+B,EAAE,MAAM,sBAAsB,CAAA;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAChD,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAC3E,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AACvC,OAAO,EAAE,8BAA8B,EAAE,MAAM,wBAAwB,CAAA;AACvE,YAAY,EACV,mBAAmB,EACnB,uBAAuB,EACvB,6BAA6B,EAC7B,cAAc,GACf,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACL,iCAAiC,EACjC,oBAAoB,EACpB,2BAA2B,EAC3B,oBAAoB,GACrB,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AACpF,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,EAAE,oCAAoC,EAAE,MAAM,yBAAyB,CAAA;AAC9E,YAAY,EACV,wBAAwB,EACxB,8BAA8B,EAC9B,2BAA2B,EAC3B,qCAAqC,EACrC,qBAAqB,EACrB,2BAA2B,EAC3B,qBAAqB,EACrB,sBAAsB,EACtB,6BAA6B,EAC7B,yBAAyB,EACzB,4BAA4B,EAC5B,WAAW,EACX,mBAAmB,GACpB,MAAM,SAAS,CAAA;AAChB,YAAY,EAAE,8BAA8B,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAA;AAC/F,OAAO,EACL,6CAA6C,EAC7C,eAAe,EACf,2BAA2B,EAC3B,sBAAsB,EACtB,2BAA2B,GAC5B,MAAM,gBAAgB,CAAA;AACvB,YAAY,EACV,gCAAgC,EAChC,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,yBAAyB,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAA;AAC5F,OAAO,EACL,oBAAoB,EACpB,wBAAwB,EACxB,0BAA0B,EAC1B,wBAAwB,EACxB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,+BAA+B,EAAE,MAAM,sBAAsB,CAAA;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAChD,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAC3E,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AACvC,OAAO,EAAE,8BAA8B,EAAE,MAAM,wBAAwB,CAAA;AACvE,YAAY,EACV,mBAAmB,EACnB,uBAAuB,EACvB,6BAA6B,EAC7B,cAAc,GACf,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACL,iCAAiC,EACjC,oBAAoB,EACpB,2BAA2B,EAC3B,oBAAoB,GACrB,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AACpF,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAC9E,YAAY,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAA;AACtE,OAAO,EACL,sBAAsB,EACtB,iCAAiC,EACjC,+BAA+B,EAC/B,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,uBAAuB,CAAA"}
|
package/build/types/types.d.ts
CHANGED
|
@@ -49,6 +49,30 @@ export interface DynamicLiveActivityEntryConfig {
|
|
|
49
49
|
*/
|
|
50
50
|
export type WidgetLocalizedCopy = Record<string, string>;
|
|
51
51
|
export type WidgetLabel = string | WidgetLocalizedCopy;
|
|
52
|
+
/**
|
|
53
|
+
* One value a configuration parameter offers. iOS shows `title` in a picker on the Edit Widget
|
|
54
|
+
* sheet; `env.configuration` receives `value`.
|
|
55
|
+
*/
|
|
56
|
+
export interface WidgetConfigurationOption {
|
|
57
|
+
value: string;
|
|
58
|
+
title: WidgetLabel;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The shape of `appIntent.parameters[]` both platform plugins share. iOS requires `title`.
|
|
62
|
+
*/
|
|
63
|
+
export interface WidgetConfigurationParameter {
|
|
64
|
+
name: string;
|
|
65
|
+
title?: WidgetLabel;
|
|
66
|
+
default?: string;
|
|
67
|
+
options?: WidgetConfigurationOption[];
|
|
68
|
+
}
|
|
69
|
+
/** The widget fields that carry configuration-sheet copy. */
|
|
70
|
+
export interface WidgetConfigurationCopySource {
|
|
71
|
+
id: string;
|
|
72
|
+
appIntent?: {
|
|
73
|
+
parameters: WidgetConfigurationParameter[];
|
|
74
|
+
};
|
|
75
|
+
}
|
|
52
76
|
/**
|
|
53
77
|
* Build-time widget initial state source: a single file path, or per-locale paths (same key rules as `WidgetLocalizedCopy`).
|
|
54
78
|
* Each path must point to a module that exports the widget variants / default export for prerendering.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,qBAAqB,GAAG,KAAK,GAAG,SAAS,CAAA;AAErD;;;;;GAKG;AACH,MAAM,WAAW,wBAAwB;IACvC,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,2BAA2B;IAC1C,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,CAAC,CAAA;IACV,QAAQ,EAAE,qBAAqB,CAAA;IAC/B,OAAO,EAAE,2BAA2B,EAAE,CAAA;CACvC;AAED;;;;GAIG;AACH,MAAM,WAAW,qCAAqC;IACpD,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,CAAC,CAAA;IACV,QAAQ,EAAE,KAAK,CAAA;IACf,cAAc,EAAE,qCAAqC,EAAE,CAAA;CACxD;AAED;;;GAGG;AACH,MAAM,WAAW,8BAA8B;IAC7C,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;CACd;AAED;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAExD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,mBAAmB,CAAA;AAEtD;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,mBAAmB,CAAA"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,qBAAqB,GAAG,KAAK,GAAG,SAAS,CAAA;AAErD;;;;;GAKG;AACH,MAAM,WAAW,wBAAwB;IACvC,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,2BAA2B;IAC1C,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,CAAC,CAAA;IACV,QAAQ,EAAE,qBAAqB,CAAA;IAC/B,OAAO,EAAE,2BAA2B,EAAE,CAAA;CACvC;AAED;;;;GAIG;AACH,MAAM,WAAW,qCAAqC;IACpD,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,CAAC,CAAA;IACV,QAAQ,EAAE,KAAK,CAAA;IACf,cAAc,EAAE,qCAAqC,EAAE,CAAA;CACxD;AAED;;;GAGG;AACH,MAAM,WAAW,8BAA8B;IAC7C,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;CACd;AAED;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAExD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,mBAAmB,CAAA;AAEtD;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,WAAW,CAAA;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC3C,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,yBAAyB,EAAE,CAAA;CACtC;AAED,6DAA6D;AAC7D,MAAM,WAAW,6BAA6B;IAC5C,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,CAAC,EAAE;QAAE,UAAU,EAAE,4BAA4B,EAAE,CAAA;KAAE,CAAA;CAC3D;AAED;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,mBAAmB,CAAA"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Locale picking now lives in `@use-voltra/core` so widget code can use the same fallback rules
|
|
3
|
+
* at render time (`resolveLocale`). Re-exported here so existing imports keep working.
|
|
4
4
|
*/
|
|
5
|
-
export
|
|
6
|
-
export declare function pickLocalizedValue(perLocale: Record<string, string>, preferredLanguages: string[]): string | undefined;
|
|
5
|
+
export { normalizeLocaleTag, pickLocalizedValue } from '@use-voltra/core/locale';
|
|
7
6
|
//# sourceMappingURL=localePick.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"localePick.d.ts","sourceRoot":"","sources":["../../../src/utils/localePick.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,
|
|
1
|
+
{"version":3,"file":"localePick.d.ts","sourceRoot":"","sources":["../../../src/utils/localePick.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { WidgetConfigurationCopySource, WidgetLabel } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* A string the operating system shows while the user configures a widget (ADR 0009 §6): a
|
|
4
|
+
* parameter title or an option label. `key` is the same on both platforms, apart from the
|
|
5
|
+
* resource-name sanitising Android needs.
|
|
6
|
+
*/
|
|
7
|
+
export interface WidgetConfigurationString {
|
|
8
|
+
key: string;
|
|
9
|
+
label: WidgetLabel;
|
|
10
|
+
}
|
|
11
|
+
type KeyPart = (value: string) => string;
|
|
12
|
+
export declare function widgetParameterTitleKey(widgetId: string, parameterName: string, part?: KeyPart): string;
|
|
13
|
+
export declare function widgetParameterOptionKey(widgetId: string, parameterName: string, optionValue: string, part?: KeyPart): string;
|
|
14
|
+
/**
|
|
15
|
+
* Every configuration-sheet string a widget declares, in declaration order. `part` sanitises each
|
|
16
|
+
* key segment (Android resource names allow only `[a-z0-9_]`).
|
|
17
|
+
*/
|
|
18
|
+
export declare function collectWidgetConfigurationStrings(widget: WidgetConfigurationCopySource, part?: KeyPart): WidgetConfigurationString[];
|
|
19
|
+
/** Locale keys used by any locale map among `labels`, `__default` excluded. */
|
|
20
|
+
export declare function collectLabelLocaleKeys(labels: Iterable<WidgetLabel | undefined>): string[];
|
|
21
|
+
/**
|
|
22
|
+
* Validates `appIntent.parameters[]` (titles as plain strings or locale maps, and static
|
|
23
|
+
* `options`).
|
|
24
|
+
*/
|
|
25
|
+
export declare function validateWidgetConfigurationCopy(widget: WidgetConfigurationCopySource, options: {
|
|
26
|
+
requireParameterTitle: boolean;
|
|
27
|
+
/** iOS compiles names into Swift properties; Android only uses them as keys. Default `true`. */
|
|
28
|
+
requireIdentifierName?: boolean;
|
|
29
|
+
}): void;
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=widgetConfiguration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widgetConfiguration.d.ts","sourceRoot":"","sources":["../../src/widgetConfiguration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,6BAA6B,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAOzE;;;;GAIG;AACH,MAAM,WAAW,yBAAyB;IACxC,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,WAAW,CAAA;CACnB;AAED,KAAK,OAAO,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;AAIxC,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,IAAI,GAAE,OAAkB,GAAG,MAAM,CAEjH;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,EACnB,IAAI,GAAE,OAAkB,GACvB,MAAM,CAER;AAED;;;GAGG;AACH,wBAAgB,iCAAiC,CAC/C,MAAM,EAAE,6BAA6B,EACrC,IAAI,GAAE,OAAkB,GACvB,yBAAyB,EAAE,CAgB7B;AAED,+EAA+E;AAC/E,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,QAAQ,CAAC,WAAW,GAAG,SAAS,CAAC,GAAG,MAAM,EAAE,CAa1F;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,6BAA6B,EACrC,OAAO,EAAE;IACP,qBAAqB,EAAE,OAAO,CAAA;IAC9B,gGAAgG;IAChG,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAChC,GACA,IAAI,CAwEN"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@use-voltra/expo-plugin",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Shared utilities for Voltra Expo config plugins",
|
|
5
5
|
"main": "build/cjs/index.js",
|
|
6
6
|
"module": "build/esm/index.js",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"README.md"
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@use-voltra/compiler": "^2.
|
|
24
|
-
"@use-voltra/core": "^2.
|
|
23
|
+
"@use-voltra/compiler": "^2.4.0",
|
|
24
|
+
"@use-voltra/core": "^2.4.0"
|
|
25
25
|
},
|
|
26
26
|
"keywords": [
|
|
27
27
|
"voltra",
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,9 @@ export type {
|
|
|
9
9
|
DynamicWidgetManifestWidget,
|
|
10
10
|
DynamicWidgetPlatform,
|
|
11
11
|
WidgetInitialStatePath,
|
|
12
|
+
WidgetConfigurationCopySource,
|
|
13
|
+
WidgetConfigurationOption,
|
|
14
|
+
WidgetConfigurationParameter,
|
|
12
15
|
WidgetLabel,
|
|
13
16
|
WidgetLocalizedCopy,
|
|
14
17
|
} from './types'
|
|
@@ -53,3 +56,11 @@ export {
|
|
|
53
56
|
} from './utils/prerender'
|
|
54
57
|
export type { WidgetModuleLoader, WidgetModulePlatform } from '@use-voltra/compiler'
|
|
55
58
|
export { isWidgetLocalizedMap, widgetLabelEnglish } from './utils/widgetLabel'
|
|
59
|
+
export type { WidgetConfigurationString } from './widgetConfiguration'
|
|
60
|
+
export {
|
|
61
|
+
collectLabelLocaleKeys,
|
|
62
|
+
collectWidgetConfigurationStrings,
|
|
63
|
+
validateWidgetConfigurationCopy,
|
|
64
|
+
widgetParameterOptionKey,
|
|
65
|
+
widgetParameterTitleKey,
|
|
66
|
+
} from './widgetConfiguration'
|
package/src/types.ts
CHANGED
|
@@ -59,6 +59,31 @@ export type WidgetLocalizedCopy = Record<string, string>
|
|
|
59
59
|
|
|
60
60
|
export type WidgetLabel = string | WidgetLocalizedCopy
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* One value a configuration parameter offers. iOS shows `title` in a picker on the Edit Widget
|
|
64
|
+
* sheet; `env.configuration` receives `value`.
|
|
65
|
+
*/
|
|
66
|
+
export interface WidgetConfigurationOption {
|
|
67
|
+
value: string
|
|
68
|
+
title: WidgetLabel
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The shape of `appIntent.parameters[]` both platform plugins share. iOS requires `title`.
|
|
73
|
+
*/
|
|
74
|
+
export interface WidgetConfigurationParameter {
|
|
75
|
+
name: string
|
|
76
|
+
title?: WidgetLabel
|
|
77
|
+
default?: string
|
|
78
|
+
options?: WidgetConfigurationOption[]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The widget fields that carry configuration-sheet copy. */
|
|
82
|
+
export interface WidgetConfigurationCopySource {
|
|
83
|
+
id: string
|
|
84
|
+
appIntent?: { parameters: WidgetConfigurationParameter[] }
|
|
85
|
+
}
|
|
86
|
+
|
|
62
87
|
/**
|
|
63
88
|
* Build-time widget initial state source: a single file path, or per-locale paths (same key rules as `WidgetLocalizedCopy`).
|
|
64
89
|
* Each path must point to a module that exports the widget variants / default export for prerendering.
|
package/src/utils/localePick.ts
CHANGED
|
@@ -1,53 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Locale picking now lives in `@use-voltra/core` so widget code can use the same fallback rules
|
|
3
|
+
* at render time (`resolveLocale`). Re-exported here so existing imports keep working.
|
|
4
4
|
*/
|
|
5
|
-
export
|
|
6
|
-
return tag.trim().toLowerCase().replace(/_/g, '-')
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function pickLocalizedValue(
|
|
10
|
-
perLocale: Record<string, string>,
|
|
11
|
-
preferredLanguages: string[]
|
|
12
|
-
): string | undefined {
|
|
13
|
-
const entries = Object.entries(perLocale).filter(([, v]) => typeof v === 'string' && v.length > 0)
|
|
14
|
-
if (entries.length === 0) {
|
|
15
|
-
return undefined
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const byNorm = new Map<string, string>()
|
|
19
|
-
for (const [k, v] of entries) {
|
|
20
|
-
byNorm.set(normalizeLocaleTag(k), v)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const DEFAULT_KEY = '__default'
|
|
24
|
-
|
|
25
|
-
for (const pref of preferredLanguages) {
|
|
26
|
-
const n = normalizeLocaleTag(pref)
|
|
27
|
-
const direct = byNorm.get(n)
|
|
28
|
-
if (direct !== undefined) {
|
|
29
|
-
return direct
|
|
30
|
-
}
|
|
31
|
-
const lang = n.split('-')[0] ?? n
|
|
32
|
-
for (const [key, val] of entries) {
|
|
33
|
-
const kn = normalizeLocaleTag(key)
|
|
34
|
-
const keyLang = kn.split('-')[0] ?? kn
|
|
35
|
-
if (keyLang === lang) {
|
|
36
|
-
return val
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const en = byNorm.get('en') ?? entries.find(([k]) => normalizeLocaleTag(k) === 'en')?.[1]
|
|
42
|
-
if (en !== undefined) {
|
|
43
|
-
return en
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const defaultVal = byNorm.get(DEFAULT_KEY) ?? entries.find(([k]) => k === DEFAULT_KEY)?.[1]
|
|
47
|
-
if (defaultVal !== undefined) {
|
|
48
|
-
return defaultVal
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const sorted = [...entries].sort(([a], [b]) => a.localeCompare(b))
|
|
52
|
-
return sorted[0]?.[1]
|
|
53
|
-
}
|
|
5
|
+
export { normalizeLocaleTag, pickLocalizedValue } from '@use-voltra/core/locale'
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { collectWidgetConfigurationStrings, validateWidgetConfigurationCopy } from './widgetConfiguration'
|
|
2
|
+
|
|
3
|
+
const widget = {
|
|
4
|
+
id: 'Weather',
|
|
5
|
+
appIntent: {
|
|
6
|
+
parameters: [
|
|
7
|
+
{
|
|
8
|
+
name: 'units',
|
|
9
|
+
title: { en: 'Units', pl: 'Jednostki' },
|
|
10
|
+
default: 'metric',
|
|
11
|
+
options: [
|
|
12
|
+
{ value: 'metric', title: { en: 'Metric', pl: 'Metryczne' } },
|
|
13
|
+
{ value: 'us-customary', title: 'US customary' },
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
{ name: 'city', default: 'Warsaw' },
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('collectWidgetConfigurationStrings', () => {
|
|
22
|
+
it('lists parameter titles and option labels under stable keys', () => {
|
|
23
|
+
expect(collectWidgetConfigurationStrings(widget).map(({ key }) => key)).toEqual([
|
|
24
|
+
'voltra_widget_Weather_param_units_title',
|
|
25
|
+
'voltra_widget_Weather_param_units_option_metric',
|
|
26
|
+
'voltra_widget_Weather_param_units_option_us-customary',
|
|
27
|
+
])
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('sanitises every key segment with the given function', () => {
|
|
31
|
+
const android = (value: string) => value.toLowerCase().replace(/[^a-z0-9_]/g, '_')
|
|
32
|
+
expect(collectWidgetConfigurationStrings(widget, android).map(({ key }) => key)).toContain(
|
|
33
|
+
'voltra_widget_weather_param_units_option_us_customary'
|
|
34
|
+
)
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe('validateWidgetConfigurationCopy', () => {
|
|
39
|
+
it('accepts locale-map titles and static options', () => {
|
|
40
|
+
expect(() => validateWidgetConfigurationCopy(widget, { requireParameterTitle: false })).not.toThrow()
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('requires a title on iOS', () => {
|
|
44
|
+
expect(() => validateWidgetConfigurationCopy(widget, { requireParameterTitle: true })).toThrow(
|
|
45
|
+
"appIntent parameter 'city' needs a title"
|
|
46
|
+
)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('rejects a default that is not one of the options', () => {
|
|
50
|
+
const invalid = {
|
|
51
|
+
id: 'w',
|
|
52
|
+
appIntent: {
|
|
53
|
+
parameters: [{ name: 'units', title: 'Units', default: 'kelvin', options: [{ value: 'metric', title: 'M' }] }],
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
expect(() => validateWidgetConfigurationCopy(invalid, { requireParameterTitle: true })).toThrow(
|
|
57
|
+
"default 'kelvin' is not one of its options"
|
|
58
|
+
)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('rejects duplicate option values, invalid names and bad locale maps', () => {
|
|
62
|
+
const base = { id: 'w' }
|
|
63
|
+
expect(() =>
|
|
64
|
+
validateWidgetConfigurationCopy(
|
|
65
|
+
{
|
|
66
|
+
...base,
|
|
67
|
+
appIntent: {
|
|
68
|
+
parameters: [
|
|
69
|
+
{
|
|
70
|
+
name: 'units',
|
|
71
|
+
title: 'Units',
|
|
72
|
+
options: [
|
|
73
|
+
{ value: 'a', title: 'A' },
|
|
74
|
+
{ value: 'a', title: 'B' },
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{ requireParameterTitle: true }
|
|
81
|
+
)
|
|
82
|
+
).toThrow("lists option 'a' twice")
|
|
83
|
+
expect(() =>
|
|
84
|
+
validateWidgetConfigurationCopy(
|
|
85
|
+
{ ...base, appIntent: { parameters: [{ name: 'my-param', title: 'x' }] } },
|
|
86
|
+
{ requireParameterTitle: true }
|
|
87
|
+
)
|
|
88
|
+
).toThrow('.name must start with a letter')
|
|
89
|
+
expect(() =>
|
|
90
|
+
validateWidgetConfigurationCopy(
|
|
91
|
+
{ ...base, appIntent: { parameters: [{ name: 'units', title: {} }] } },
|
|
92
|
+
{ requireParameterTitle: true }
|
|
93
|
+
)
|
|
94
|
+
).toThrow("appIntent parameter 'units' title locale map must not be empty")
|
|
95
|
+
})
|
|
96
|
+
})
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type { WidgetConfigurationCopySource, WidgetLabel } from './types'
|
|
2
|
+
import { isWidgetLocalizedMap } from './utils/widgetLabel'
|
|
3
|
+
import { validateWidgetLabel } from './validation'
|
|
4
|
+
|
|
5
|
+
/** Swift property / Kotlin-safe identifier, also the key under `env.configuration`. */
|
|
6
|
+
const PARAMETER_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A string the operating system shows while the user configures a widget (ADR 0009 §6): a
|
|
10
|
+
* parameter title or an option label. `key` is the same on both platforms, apart from the
|
|
11
|
+
* resource-name sanitising Android needs.
|
|
12
|
+
*/
|
|
13
|
+
export interface WidgetConfigurationString {
|
|
14
|
+
key: string
|
|
15
|
+
label: WidgetLabel
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type KeyPart = (value: string) => string
|
|
19
|
+
|
|
20
|
+
const identity: KeyPart = (value) => value
|
|
21
|
+
|
|
22
|
+
export function widgetParameterTitleKey(widgetId: string, parameterName: string, part: KeyPart = identity): string {
|
|
23
|
+
return `voltra_widget_${part(widgetId)}_param_${part(parameterName)}_title`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function widgetParameterOptionKey(
|
|
27
|
+
widgetId: string,
|
|
28
|
+
parameterName: string,
|
|
29
|
+
optionValue: string,
|
|
30
|
+
part: KeyPart = identity
|
|
31
|
+
): string {
|
|
32
|
+
return `voltra_widget_${part(widgetId)}_param_${part(parameterName)}_option_${part(optionValue)}`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Every configuration-sheet string a widget declares, in declaration order. `part` sanitises each
|
|
37
|
+
* key segment (Android resource names allow only `[a-z0-9_]`).
|
|
38
|
+
*/
|
|
39
|
+
export function collectWidgetConfigurationStrings(
|
|
40
|
+
widget: WidgetConfigurationCopySource,
|
|
41
|
+
part: KeyPart = identity
|
|
42
|
+
): WidgetConfigurationString[] {
|
|
43
|
+
const strings: WidgetConfigurationString[] = []
|
|
44
|
+
|
|
45
|
+
for (const parameter of widget.appIntent?.parameters ?? []) {
|
|
46
|
+
if (parameter.title !== undefined) {
|
|
47
|
+
strings.push({ key: widgetParameterTitleKey(widget.id, parameter.name, part), label: parameter.title })
|
|
48
|
+
}
|
|
49
|
+
for (const option of parameter.options ?? []) {
|
|
50
|
+
strings.push({
|
|
51
|
+
key: widgetParameterOptionKey(widget.id, parameter.name, option.value, part),
|
|
52
|
+
label: option.title,
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return strings
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Locale keys used by any locale map among `labels`, `__default` excluded. */
|
|
61
|
+
export function collectLabelLocaleKeys(labels: Iterable<WidgetLabel | undefined>): string[] {
|
|
62
|
+
const keys = new Set<string>()
|
|
63
|
+
for (const label of labels) {
|
|
64
|
+
if (label === undefined || !isWidgetLocalizedMap(label)) {
|
|
65
|
+
continue
|
|
66
|
+
}
|
|
67
|
+
for (const [locale, value] of Object.entries(label)) {
|
|
68
|
+
if (locale !== '__default' && typeof value === 'string' && value.trim()) {
|
|
69
|
+
keys.add(locale)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return [...keys]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Validates `appIntent.parameters[]` (titles as plain strings or locale maps, and static
|
|
78
|
+
* `options`).
|
|
79
|
+
*/
|
|
80
|
+
export function validateWidgetConfigurationCopy(
|
|
81
|
+
widget: WidgetConfigurationCopySource,
|
|
82
|
+
options: {
|
|
83
|
+
requireParameterTitle: boolean
|
|
84
|
+
/** iOS compiles names into Swift properties; Android only uses them as keys. Default `true`. */
|
|
85
|
+
requireIdentifierName?: boolean
|
|
86
|
+
}
|
|
87
|
+
): void {
|
|
88
|
+
const widgetId = widget.id
|
|
89
|
+
|
|
90
|
+
const appIntent = widget.appIntent as unknown
|
|
91
|
+
if (appIntent === undefined) {
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
if (typeof appIntent !== 'object' || appIntent === null || !Array.isArray((appIntent as any).parameters)) {
|
|
95
|
+
throw new Error(`Widget '${widgetId}': appIntent.parameters must be an array`)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const names = new Set<string>()
|
|
99
|
+
for (const [index, parameter] of (appIntent as { parameters: unknown[] }).parameters.entries()) {
|
|
100
|
+
const context = `appIntent.parameters[${index}]`
|
|
101
|
+
if (typeof parameter !== 'object' || parameter === null) {
|
|
102
|
+
throw new Error(`Widget '${widgetId}': ${context} must be an object`)
|
|
103
|
+
}
|
|
104
|
+
const { name, title, default: defaultValue, options: parameterOptions } = parameter as Record<string, unknown>
|
|
105
|
+
|
|
106
|
+
if (typeof name !== 'string' || !name) {
|
|
107
|
+
throw new Error(`Widget '${widgetId}': ${context}.name must be a non-empty string`)
|
|
108
|
+
}
|
|
109
|
+
if ((options.requireIdentifierName ?? true) && !PARAMETER_NAME_PATTERN.test(name)) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Widget '${widgetId}': ${context}.name must start with a letter or underscore and contain only letters, digits and underscores`
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
if (names.has(name)) {
|
|
115
|
+
throw new Error(`Widget '${widgetId}': appIntent parameter '${name}' is declared more than once`)
|
|
116
|
+
}
|
|
117
|
+
names.add(name)
|
|
118
|
+
|
|
119
|
+
if (title === undefined) {
|
|
120
|
+
if (options.requireParameterTitle) {
|
|
121
|
+
throw new Error(`Widget '${widgetId}': appIntent parameter '${name}' needs a title`)
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
validateWidgetLabel(title, widgetId, `appIntent parameter '${name}' title`)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (defaultValue !== undefined && typeof defaultValue !== 'string') {
|
|
128
|
+
throw new Error(`Widget '${widgetId}': appIntent parameter '${name}' default must be a string`)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (parameterOptions === undefined) {
|
|
132
|
+
continue
|
|
133
|
+
}
|
|
134
|
+
if (!Array.isArray(parameterOptions) || parameterOptions.length === 0) {
|
|
135
|
+
throw new Error(`Widget '${widgetId}': appIntent parameter '${name}' options must be a non-empty array`)
|
|
136
|
+
}
|
|
137
|
+
const values = new Set<string>()
|
|
138
|
+
for (const option of parameterOptions) {
|
|
139
|
+
const value = (option as Record<string, unknown> | null)?.value
|
|
140
|
+
if (typeof value !== 'string' || !value) {
|
|
141
|
+
throw new Error(`Widget '${widgetId}': every option of appIntent parameter '${name}' needs a string value`)
|
|
142
|
+
}
|
|
143
|
+
if (values.has(value)) {
|
|
144
|
+
throw new Error(`Widget '${widgetId}': appIntent parameter '${name}' lists option '${value}' twice`)
|
|
145
|
+
}
|
|
146
|
+
values.add(value)
|
|
147
|
+
validateWidgetLabel(
|
|
148
|
+
(option as Record<string, unknown>).title,
|
|
149
|
+
widgetId,
|
|
150
|
+
`appIntent parameter '${name}' option '${value}' title`
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
if (defaultValue !== undefined && !values.has(defaultValue)) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`Widget '${widgetId}': appIntent parameter '${name}' default '${defaultValue}' is not one of its options`
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|