@use-voltra/expo-plugin 2.3.2 → 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.
@@ -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
@@ -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;AAc7C,+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"}
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 = normalizeLocaleTag;
4
- exports.pickLocalizedValue = pickLocalizedValue;
3
+ exports.pickLocalizedValue = exports.normalizeLocaleTag = void 0;
5
4
  /**
6
- * Picks a localized string value from a locale-keyed map using the same fallback rules as iOS/Android runtimes:
7
- * preferred language tags (full match, then language-only), then `en`, then `__default`, then first value.
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
- function normalizeLocaleTag(tag) {
10
- return tag.trim().toLowerCase().replace(/_/g, '-');
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":";;AAIA,gDAEC;AAED,gDA4CC;AApDD;;;GAGG;AACH,SAAgB,kBAAkB,CAAC,GAAW;IAC5C,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;AACpD,CAAC;AAED,SAAgB,kBAAkB,CAChC,SAAiC,EACjC,kBAA4B;IAE5B,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAClG,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAA;IACxC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACtC,CAAC;IAED,MAAM,WAAW,GAAG,WAAW,CAAA;IAE/B,KAAK,MAAM,IAAI,IAAI,kBAAkB,EAAE,CAAC;QACtC,MAAM,CAAC,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAA;QAClC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;QAC5B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,MAAM,CAAA;QACf,CAAC;QACD,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;QACjC,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,OAAO,EAAE,CAAC;YACjC,MAAM,EAAE,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAA;YAClC,MAAM,OAAO,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;YACtC,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBACrB,OAAO,GAAG,CAAA;YACZ,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IACzF,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;QACrB,OAAO,EAAE,CAAA;IACX,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IAC3F,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO,UAAU,CAAA;IACnB,CAAC;IAED,MAAM,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAA;IAClE,OAAO,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;AACvB,CAAC"}
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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"}
@@ -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
@@ -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;AAc9E,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"}
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
- * Picks a localized string value from a locale-keyed map using the same fallback rules as iOS/Android runtimes:
3
- * preferred language tags (full match, then language-only), then `en`, then `__default`, then first value.
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 function normalizeLocaleTag(tag) {
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,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAC5C,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;AACpD,CAAC;AAED,MAAM,UAAU,kBAAkB,CAChC,SAAiC,EACjC,kBAA4B;IAE5B,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAClG,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAA;IACxC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACtC,CAAC;IAED,MAAM,WAAW,GAAG,WAAW,CAAA;IAE/B,KAAK,MAAM,IAAI,IAAI,kBAAkB,EAAE,CAAC;QACtC,MAAM,CAAC,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAA;QAClC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;QAC5B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,MAAM,CAAA;QACf,CAAC;QACD,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;QACjC,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,OAAO,EAAE,CAAC;YACjC,MAAM,EAAE,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAA;YAClC,MAAM,OAAO,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;YACtC,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBACrB,OAAO,GAAG,CAAA;YACZ,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IACzF,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;QACrB,OAAO,EAAE,CAAA;IACX,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IAC3F,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO,UAAU,CAAA;IACnB,CAAC;IAED,MAAM,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAA;IAClE,OAAO,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;AACvB,CAAC"}
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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"}
@@ -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"}
@@ -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
- * Picks a localized string value from a locale-keyed map using the same fallback rules as iOS/Android runtimes:
3
- * preferred language tags (full match, then language-only), then `en`, then `__default`, then first value.
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 declare function normalizeLocaleTag(tag: string): string;
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,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACjC,kBAAkB,EAAE,MAAM,EAAE,GAC3B,MAAM,GAAG,SAAS,CAyCpB"}
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.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.3.2",
24
- "@use-voltra/core": "^2.3.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.
@@ -1,53 +1,5 @@
1
1
  /**
2
- * Picks a localized string value from a locale-keyed map using the same fallback rules as iOS/Android runtimes:
3
- * preferred language tags (full match, then language-only), then `en`, then `__default`, then first value.
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 function normalizeLocaleTag(tag: string): string {
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
+ }