@colixsystems/widget-sdk 0.270.0 → 0.271.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -1
- package/dist/basic-style.js +37 -0
- package/dist/contract.cjs +36 -1
- package/dist/contract.js +36 -1
- package/dist/index.d.ts +21 -0
- package/dist/index.js +2 -0
- package/dist/index.native.js +2 -0
- package/dist/linter.cjs +30 -11
- package/dist/linter.js +29 -11
- package/package.json +1 -1
- package/src/contract.js +36 -1
package/README.md
CHANGED
|
@@ -86,7 +86,11 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
86
86
|
|
|
87
87
|
## Status
|
|
88
88
|
|
|
89
|
-
`v0.
|
|
89
|
+
`v0.271.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
|
|
90
|
+
|
|
91
|
+
### What's new in 0.271.0 (contract 1.221.0)
|
|
92
|
+
|
|
93
|
+
**Basic styling in one call.** Spread `BASIC_STYLE_SCHEMA` into `manifest.styleSchema` and call `basicStyle(style)` in the component: the five basic style fields (`background`, `textColor`, `align`, `padding`, `margin`) are declared and applied without wiring each by hand. See *Basic styling* under *Design & visual polish*.
|
|
90
94
|
|
|
91
95
|
### What's new in 0.270.0 (contract 1.220.0)
|
|
92
96
|
|
|
@@ -3345,6 +3349,22 @@ A widget that works but looks unfinished is only half done. `useTheme()` is the
|
|
|
3345
3349
|
|
|
3346
3350
|
**Decide before you build:** the widget's shape (card, list, form, control), its hierarchy (the one thing the eye lands on first), its single accent moment, and its empty/loading/error look — then compose. Specific decisions make a distinctive widget; leaving them to default makes a generic one.
|
|
3347
3351
|
|
|
3352
|
+
- **Basic styling: one schema, one call.** Every generated widget offers five whole-widget style fields: `background`, `textColor`, `align`, `padding` and `margin`. Spread `BASIC_STYLE_SCHEMA` into your manifest's `styleSchema`, then apply them with `basicStyle(style)`. It returns `{ root, text }`: spread `root` on the outermost `View` (background, align as `alignItems`, per-side padding and margin) and `text` on each `Text` in a theme text colour. A field the author left unset emits nothing, so your theme-based styles show through. `appstudio-widget lint` counts the call as reading all five. When `submit_widget_source` or the Studio's widget builder publishes a widget that calls `basicStyle()`, any of the five its manifest leaves out is declared for it.
|
|
3353
|
+
|
|
3354
|
+
```jsx
|
|
3355
|
+
import { View, Text, basicStyle, BASIC_STYLE_SCHEMA } from "@colixsystems/widget-sdk";
|
|
3356
|
+
|
|
3357
|
+
export const manifest = { /* … */ styleSchema: { ...BASIC_STYLE_SCHEMA, titleColor: { type: "color", label: "Title", ui: { group: "Title" } } } };
|
|
3358
|
+
|
|
3359
|
+
export default function Hero({ title, style = {} }) {
|
|
3360
|
+
const basic = basicStyle(style);
|
|
3361
|
+
return (
|
|
3362
|
+
<View style={[styles.root, basic.root]}>
|
|
3363
|
+
<Text style={[styles.title, basic.text, style.titleColor && { color: style.titleColor }]}>{title}</Text>
|
|
3364
|
+
</View>
|
|
3365
|
+
);
|
|
3366
|
+
}
|
|
3367
|
+
```
|
|
3348
3368
|
- **Pull spacing and corners from tokens.** Use `theme.spacing` (`xs / sm / md / lg / xl`) for a consistent padding and gap rhythm, and `theme.radii` (`sm / md / lg / pill`) for corners — `radii.lg` for cards and hero surfaces, `radii.md` for controls nested inside one. Don't hardcode raw pixel values.
|
|
3349
3369
|
- **Build a hierarchy.** A clear title (large, bold, `colors.onSurface`), body text, and muted captions in `colors.onSurfaceMuted` — three weights, not one flat size. Reserve full-strength `colors.primary` (with `colors.onPrimary` for text on it) for the single most important action or metric.
|
|
3350
3370
|
- **Style text by its ROLE, not by inventing a size and weight.** `theme.typography.roles` carries the workspace's type scale as six semantic tiers — `display`, `title`, `subtitle`, `body`, `caption`, `overline` — each already resolved to a `fontSize`, `fontWeight`, `fontStyle`, `lineHeight` (a MULTIPLE of the size), `letterSpacing`, `textTransform` and a `colorToken`. Pick the tier the text IS, rather than choosing `fontSize: 16, fontWeight: "600"` yourself: your widget's section title is `title`, each card or row title is `subtitle`, running text is `body`, timestamps and helper text are `caption`, a small kicker or column label is `overline`, and a headline stat is `display`. This is what stops one widget rendering a subtitle bold while the widget beside it renders one italic, and it lets the workspace re-tune every subtitle in the app from Theme Settings in one edit. **Two tokens need converting rather than copying** — `lineHeight` is a multiple (React Native wants absolute pixels, so the raw `1.15` gives you a one-pixel line box) and `colorToken` names a colour rather than being one:
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// The five style fields every generated widget declares, in one call: spread
|
|
2
|
+
// BASIC_STYLE_SCHEMA into `manifest.styleSchema`, apply `basicStyle(style)`.
|
|
3
|
+
// Platform-free, so the web and native entries export this one module.
|
|
4
|
+
|
|
5
|
+
import { CONTRACT } from "./contract.js";
|
|
6
|
+
import { spacingOverlay } from "./card-style.js";
|
|
7
|
+
|
|
8
|
+
export const BASIC_STYLE_SCHEMA = CONTRACT.basicStyleSchema;
|
|
9
|
+
|
|
10
|
+
const ALIGN_ITEMS = Object.freeze({
|
|
11
|
+
left: "flex-start",
|
|
12
|
+
center: "center",
|
|
13
|
+
right: "flex-end",
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The basic style fields as React Native styles. Each is emitted only when the
|
|
18
|
+
* author set it, so the widget's own theme-based styles show through.
|
|
19
|
+
*
|
|
20
|
+
* @param {object} [style] the widget's `props.style`
|
|
21
|
+
* @returns {{ root: object, text: object|null }} `root` for the outermost
|
|
22
|
+
* View (backgroundColor, alignItems, padding, margin); `text` (`{ color }`)
|
|
23
|
+
* for each Text coloured by a theme text token.
|
|
24
|
+
*/
|
|
25
|
+
export function basicStyle(style) {
|
|
26
|
+
const s = style || {};
|
|
27
|
+
const alignItems = ALIGN_ITEMS[s.align];
|
|
28
|
+
return {
|
|
29
|
+
root: {
|
|
30
|
+
...(s.background ? { backgroundColor: s.background } : null),
|
|
31
|
+
...(alignItems ? { alignItems } : null),
|
|
32
|
+
...spacingOverlay(s.padding, "padding"),
|
|
33
|
+
...spacingOverlay(s.margin, "margin"),
|
|
34
|
+
},
|
|
35
|
+
text: s.textColor ? { color: s.textColor } : null,
|
|
36
|
+
};
|
|
37
|
+
}
|
package/dist/contract.cjs
CHANGED
|
@@ -389,6 +389,38 @@ const THEME_SPACING_SCALE = Object.freeze({
|
|
|
389
389
|
default: 1,
|
|
390
390
|
});
|
|
391
391
|
|
|
392
|
+
// The style fields every generated widget declares, with the controls the
|
|
393
|
+
// Studio renders for them. One source for `BASIC_STYLE_SCHEMA` /
|
|
394
|
+
// `basicStyle()`, the linter, the publish gate and the Mason plan gate.
|
|
395
|
+
const BASIC_STYLE_GROUP = Object.freeze({ group: "Basics" });
|
|
396
|
+
const BASIC_SPACING_RANGE = Object.freeze({ min: 0, max: 64 });
|
|
397
|
+
const BASIC_STYLE_SCHEMA = Object.freeze({
|
|
398
|
+
background: Object.freeze({ type: "color", label: "Background", ui: BASIC_STYLE_GROUP }),
|
|
399
|
+
textColor: Object.freeze({ type: "color", label: "Text color", ui: BASIC_STYLE_GROUP }),
|
|
400
|
+
align: Object.freeze({
|
|
401
|
+
type: "select",
|
|
402
|
+
label: "Align",
|
|
403
|
+
enum: Object.freeze([
|
|
404
|
+
Object.freeze({ value: "left", label: "Left" }),
|
|
405
|
+
Object.freeze({ value: "center", label: "Center" }),
|
|
406
|
+
Object.freeze({ value: "right", label: "Right" }),
|
|
407
|
+
]),
|
|
408
|
+
ui: BASIC_STYLE_GROUP,
|
|
409
|
+
}),
|
|
410
|
+
padding: Object.freeze({
|
|
411
|
+
type: "spacing",
|
|
412
|
+
label: "Padding",
|
|
413
|
+
validation: BASIC_SPACING_RANGE,
|
|
414
|
+
ui: BASIC_STYLE_GROUP,
|
|
415
|
+
}),
|
|
416
|
+
margin: Object.freeze({
|
|
417
|
+
type: "spacing",
|
|
418
|
+
label: "Margin",
|
|
419
|
+
validation: BASIC_SPACING_RANGE,
|
|
420
|
+
ui: BASIC_STYLE_GROUP,
|
|
421
|
+
}),
|
|
422
|
+
});
|
|
423
|
+
|
|
392
424
|
// The card-surface field names shared by every widget that paints its own card
|
|
393
425
|
// (frontend/src/components/widgets/_shared/cardStyle.js CARD_STYLE_SCHEMA).
|
|
394
426
|
const CARD_SURFACE_FIELDS = Object.freeze({
|
|
@@ -6508,7 +6540,9 @@ const CONTRACT = deepFreeze({
|
|
|
6508
6540
|
// every query, record, figure and aggregate on that table, app-wide.
|
|
6509
6541
|
// 1.220.0: additive (sc-14423) — `live: true` on useDatastoreQuery,
|
|
6510
6542
|
// useDatastoreRecord, useDatastoreFigure and useDatastoreAggregate.
|
|
6511
|
-
|
|
6543
|
+
// 1.221.0: additive (sc-14460) — `basicStyleSchema`, exported as
|
|
6544
|
+
// `BASIC_STYLE_SCHEMA`, and `basicStyle(style)`, which applies it.
|
|
6545
|
+
version: "1.221.0",
|
|
6512
6546
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6513
6547
|
hooks: HOOKS,
|
|
6514
6548
|
primitives: PRIMITIVES,
|
|
@@ -6533,6 +6567,7 @@ const CONTRACT = deepFreeze({
|
|
|
6533
6567
|
themeSpacingScale: THEME_SPACING_SCALE,
|
|
6534
6568
|
themeMenuTypes: THEME_MENU_TYPES,
|
|
6535
6569
|
themeWidgetStyles: THEME_WIDGET_STYLES,
|
|
6570
|
+
basicStyleSchema: BASIC_STYLE_SCHEMA,
|
|
6536
6571
|
widgetContextShape: WIDGET_CONTEXT_SHAPE,
|
|
6537
6572
|
bundleExportContract: BUNDLE_EXPORT_CONTRACT,
|
|
6538
6573
|
// sc-10829 — the hook a `badgeCounter: true` entry exports by name.
|
package/dist/contract.js
CHANGED
|
@@ -386,6 +386,38 @@ const THEME_SPACING_SCALE = Object.freeze({
|
|
|
386
386
|
default: 1,
|
|
387
387
|
});
|
|
388
388
|
|
|
389
|
+
// The style fields every generated widget declares, with the controls the
|
|
390
|
+
// Studio renders for them. One source for `BASIC_STYLE_SCHEMA` /
|
|
391
|
+
// `basicStyle()`, the linter, the publish gate and the Mason plan gate.
|
|
392
|
+
const BASIC_STYLE_GROUP = Object.freeze({ group: "Basics" });
|
|
393
|
+
const BASIC_SPACING_RANGE = Object.freeze({ min: 0, max: 64 });
|
|
394
|
+
const BASIC_STYLE_SCHEMA = Object.freeze({
|
|
395
|
+
background: Object.freeze({ type: "color", label: "Background", ui: BASIC_STYLE_GROUP }),
|
|
396
|
+
textColor: Object.freeze({ type: "color", label: "Text color", ui: BASIC_STYLE_GROUP }),
|
|
397
|
+
align: Object.freeze({
|
|
398
|
+
type: "select",
|
|
399
|
+
label: "Align",
|
|
400
|
+
enum: Object.freeze([
|
|
401
|
+
Object.freeze({ value: "left", label: "Left" }),
|
|
402
|
+
Object.freeze({ value: "center", label: "Center" }),
|
|
403
|
+
Object.freeze({ value: "right", label: "Right" }),
|
|
404
|
+
]),
|
|
405
|
+
ui: BASIC_STYLE_GROUP,
|
|
406
|
+
}),
|
|
407
|
+
padding: Object.freeze({
|
|
408
|
+
type: "spacing",
|
|
409
|
+
label: "Padding",
|
|
410
|
+
validation: BASIC_SPACING_RANGE,
|
|
411
|
+
ui: BASIC_STYLE_GROUP,
|
|
412
|
+
}),
|
|
413
|
+
margin: Object.freeze({
|
|
414
|
+
type: "spacing",
|
|
415
|
+
label: "Margin",
|
|
416
|
+
validation: BASIC_SPACING_RANGE,
|
|
417
|
+
ui: BASIC_STYLE_GROUP,
|
|
418
|
+
}),
|
|
419
|
+
});
|
|
420
|
+
|
|
389
421
|
// The card-surface field names shared by every widget that paints its own card
|
|
390
422
|
// (frontend/src/components/widgets/_shared/cardStyle.js CARD_STYLE_SCHEMA).
|
|
391
423
|
const CARD_SURFACE_FIELDS = Object.freeze({
|
|
@@ -6505,7 +6537,9 @@ const CONTRACT = deepFreeze({
|
|
|
6505
6537
|
// every query, record, figure and aggregate on that table, app-wide.
|
|
6506
6538
|
// 1.220.0: additive (sc-14423) — `live: true` on useDatastoreQuery,
|
|
6507
6539
|
// useDatastoreRecord, useDatastoreFigure and useDatastoreAggregate.
|
|
6508
|
-
|
|
6540
|
+
// 1.221.0: additive (sc-14460) — `basicStyleSchema`, exported as
|
|
6541
|
+
// `BASIC_STYLE_SCHEMA`, and `basicStyle(style)`, which applies it.
|
|
6542
|
+
version: "1.221.0",
|
|
6509
6543
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6510
6544
|
hooks: HOOKS,
|
|
6511
6545
|
primitives: PRIMITIVES,
|
|
@@ -6530,6 +6564,7 @@ const CONTRACT = deepFreeze({
|
|
|
6530
6564
|
themeSpacingScale: THEME_SPACING_SCALE,
|
|
6531
6565
|
themeMenuTypes: THEME_MENU_TYPES,
|
|
6532
6566
|
themeWidgetStyles: THEME_WIDGET_STYLES,
|
|
6567
|
+
basicStyleSchema: BASIC_STYLE_SCHEMA,
|
|
6533
6568
|
widgetContextShape: WIDGET_CONTEXT_SHAPE,
|
|
6534
6569
|
bundleExportContract: BUNDLE_EXPORT_CONTRACT,
|
|
6535
6570
|
// sc-10829 — the hook a `badgeCounter: true` entry exports by name.
|
package/dist/index.d.ts
CHANGED
|
@@ -5576,6 +5576,27 @@ export function roleColor(tokens: any, colors: any): string | undefined;
|
|
|
5576
5576
|
*/
|
|
5577
5577
|
export function textRoleStyle(role: TextRole | string, typography: any, colors?: any): Record<string, any>;
|
|
5578
5578
|
|
|
5579
|
+
/**
|
|
5580
|
+
* The five style fields every generated widget declares — `background`,
|
|
5581
|
+
* `textColor`, `align`, `padding` and `margin` — as styleSchema entries.
|
|
5582
|
+
* Spread it into `manifest.styleSchema`; `basicStyle()` applies it.
|
|
5583
|
+
*/
|
|
5584
|
+
export const BASIC_STYLE_SCHEMA: Readonly<WidgetPropertySchema>;
|
|
5585
|
+
/**
|
|
5586
|
+
* The basic style fields as React Native styles, each only when set. Spread
|
|
5587
|
+
* `root` on the outermost View and `text` on each Text in a theme text colour.
|
|
5588
|
+
*
|
|
5589
|
+
* @example
|
|
5590
|
+
* const basic = basicStyle(style);
|
|
5591
|
+
* <View style={[styles.root, basic.root]}>
|
|
5592
|
+
* <Text style={[styles.title, basic.text]}>{title}</Text>
|
|
5593
|
+
* </View>
|
|
5594
|
+
*/
|
|
5595
|
+
export function basicStyle(style?: Record<string, any> | null): {
|
|
5596
|
+
root: Record<string, any>;
|
|
5597
|
+
text: { color: string } | null;
|
|
5598
|
+
};
|
|
5599
|
+
|
|
5579
5600
|
// ----------------------------------------------------- formatted content
|
|
5580
5601
|
// sc-6970 — widgets have no HTML path: they render through React Native
|
|
5581
5602
|
// primitives, which have no `dangerouslySetInnerHTML` on either host. Content
|
package/dist/index.js
CHANGED
|
@@ -268,6 +268,8 @@ export {
|
|
|
268
268
|
gradientProps,
|
|
269
269
|
cardGradientProps,
|
|
270
270
|
} from "./card-style.js";
|
|
271
|
+
// The five style fields every generated widget declares, in one call.
|
|
272
|
+
export { BASIC_STYLE_SCHEMA, basicStyle } from "./basic-style.js";
|
|
271
273
|
// The themed button; follows the theme's button, destructive and ring scopes.
|
|
272
274
|
export { Button, IconButton, BUTTON_VARIANTS } from "./button.js";
|
|
273
275
|
export {
|
package/dist/index.native.js
CHANGED
|
@@ -261,6 +261,8 @@ export {
|
|
|
261
261
|
gradientProps,
|
|
262
262
|
cardGradientProps,
|
|
263
263
|
} from "./card-style.js";
|
|
264
|
+
// The five style fields every generated widget declares, in one call.
|
|
265
|
+
export { BASIC_STYLE_SCHEMA, basicStyle } from "./basic-style.js";
|
|
264
266
|
// The themed button; follows the theme's button, destructive and ring scopes.
|
|
265
267
|
export { Button, IconButton, BUTTON_VARIANTS } from "./button.native.js";
|
|
266
268
|
export {
|
package/dist/linter.cjs
CHANGED
|
@@ -1946,11 +1946,15 @@ function lintStyleWiring(manifest, files) {
|
|
|
1946
1946
|
const code = sources
|
|
1947
1947
|
.map((s) => _stripNonCode(s, { keepStrings: true }))
|
|
1948
1948
|
.join("\n");
|
|
1949
|
+
const appliedByHelper = callsBasicStyle(sources)
|
|
1950
|
+
? _WHOLE_WIDGET_STYLE_FIELDS
|
|
1951
|
+
: new Set();
|
|
1949
1952
|
const findings = [];
|
|
1950
1953
|
for (const field of Object.keys(schema)) {
|
|
1951
1954
|
// A key that is not a bare identifier cannot be scanned by name; the
|
|
1952
1955
|
// manifest validator rejects those anyway.
|
|
1953
1956
|
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(field)) continue;
|
|
1957
|
+
if (appliedByHelper.has(field)) continue;
|
|
1954
1958
|
if (new RegExp(`\\b${field}\\b`).test(code)) continue;
|
|
1955
1959
|
findings.push({
|
|
1956
1960
|
rule: "style-field-unread",
|
|
@@ -1961,7 +1965,8 @@ function lintStyleWiring(manifest, files) {
|
|
|
1961
1965
|
`— per instance, and app-wide under Design -> Widget appearance — ` +
|
|
1962
1966
|
`moves nothing. Read it from props.style and apply it ONLY when ` +
|
|
1963
1967
|
`set, so the theme still shows through while the author has chosen ` +
|
|
1964
|
-
`none. If it is not styleable, drop it from styleSchema instead
|
|
1968
|
+
`none. If it is not styleable, drop it from styleSchema instead.` +
|
|
1969
|
+
(_WHOLE_WIDGET_STYLE_FIELDS.has(field) ? _BASIC_STYLE_FIX : ""),
|
|
1965
1970
|
line: 0,
|
|
1966
1971
|
snippet: field,
|
|
1967
1972
|
});
|
|
@@ -1970,6 +1975,26 @@ function lintStyleWiring(manifest, files) {
|
|
|
1970
1975
|
return { ok: !hasErrors, findings };
|
|
1971
1976
|
}
|
|
1972
1977
|
|
|
1978
|
+
const _BASIC_STYLE_CALL = /\bbasicStyle\s*\(/;
|
|
1979
|
+
|
|
1980
|
+
const _BASIC_STYLE_FIX =
|
|
1981
|
+
` The one-line fix for every basic field: basicStyle(props.style) from ` +
|
|
1982
|
+
`@colixsystems/widget-sdk — spread its .root on the outermost View and ` +
|
|
1983
|
+
`its .text on each Text in a theme text colour.`;
|
|
1984
|
+
|
|
1985
|
+
/**
|
|
1986
|
+
* Whether the bundle calls `basicStyle()`, which applies every basic style
|
|
1987
|
+
* field (`CONTRACT.basicStyleSchema`) in one call.
|
|
1988
|
+
*
|
|
1989
|
+
* @param {object|string[]|string} files bundle sources, as `lintStyleWiring` takes them
|
|
1990
|
+
* @returns {boolean}
|
|
1991
|
+
*/
|
|
1992
|
+
function callsBasicStyle(files) {
|
|
1993
|
+
return _bundleSources(files).some((s) =>
|
|
1994
|
+
_BASIC_STYLE_CALL.test(_stripNonCode(s)),
|
|
1995
|
+
);
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1973
1998
|
// sc-7799 — a group name that groups everything defeats the rule: one
|
|
1974
1999
|
// catch-all fieldset reads exactly like the flat list it replaced.
|
|
1975
2000
|
const _GENERIC_STYLE_GROUPS = new Set([
|
|
@@ -1982,16 +2007,9 @@ const _GENERIC_STYLE_GROUPS = new Set([
|
|
|
1982
2007
|
]);
|
|
1983
2008
|
|
|
1984
2009
|
// The five the basic-style gate mandates; they restyle the widget as a whole.
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
const _WHOLE_WIDGET_STYLE_FIELDS = new Set([
|
|
1989
|
-
"background",
|
|
1990
|
-
"textColor",
|
|
1991
|
-
"align",
|
|
1992
|
-
"padding",
|
|
1993
|
-
"margin",
|
|
1994
|
-
]);
|
|
2010
|
+
const _WHOLE_WIDGET_STYLE_FIELDS = new Set(
|
|
2011
|
+
Object.keys(CONTRACT.basicStyleSchema),
|
|
2012
|
+
);
|
|
1995
2013
|
|
|
1996
2014
|
// A second element an author would style apart from the text. Pressable and
|
|
1997
2015
|
// Button are deliberately absent: a touch target wrapping the only label is
|
|
@@ -2385,6 +2403,7 @@ function lintSource(source, options) {
|
|
|
2385
2403
|
module.exports = {
|
|
2386
2404
|
lintSource,
|
|
2387
2405
|
lintStyleWiring,
|
|
2406
|
+
callsBasicStyle,
|
|
2388
2407
|
lintEditability,
|
|
2389
2408
|
bannedIdentifiers,
|
|
2390
2409
|
narrowManifestForFile,
|
package/dist/linter.js
CHANGED
|
@@ -2009,11 +2009,15 @@ export function lintStyleWiring(manifest, files) {
|
|
|
2009
2009
|
const code = sources
|
|
2010
2010
|
.map((s) => _stripNonCode(s, { keepStrings: true }))
|
|
2011
2011
|
.join("\n");
|
|
2012
|
+
const appliedByHelper = callsBasicStyle(sources)
|
|
2013
|
+
? _WHOLE_WIDGET_STYLE_FIELDS
|
|
2014
|
+
: new Set();
|
|
2012
2015
|
const findings = [];
|
|
2013
2016
|
for (const field of Object.keys(schema)) {
|
|
2014
2017
|
// A key that is not a bare identifier cannot be scanned by name; the
|
|
2015
2018
|
// manifest validator rejects those anyway.
|
|
2016
2019
|
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(field)) continue;
|
|
2020
|
+
if (appliedByHelper.has(field)) continue;
|
|
2017
2021
|
if (new RegExp(`\\b${field}\\b`).test(code)) continue;
|
|
2018
2022
|
findings.push({
|
|
2019
2023
|
rule: "style-field-unread",
|
|
@@ -2024,7 +2028,8 @@ export function lintStyleWiring(manifest, files) {
|
|
|
2024
2028
|
`— per instance, and app-wide under Design -> Widget appearance — ` +
|
|
2025
2029
|
`moves nothing. Read it from props.style and apply it ONLY when ` +
|
|
2026
2030
|
`set, so the theme still shows through while the author has chosen ` +
|
|
2027
|
-
`none. If it is not styleable, drop it from styleSchema instead
|
|
2031
|
+
`none. If it is not styleable, drop it from styleSchema instead.` +
|
|
2032
|
+
(_WHOLE_WIDGET_STYLE_FIELDS.has(field) ? _BASIC_STYLE_FIX : ""),
|
|
2028
2033
|
line: 0,
|
|
2029
2034
|
snippet: field,
|
|
2030
2035
|
});
|
|
@@ -2033,6 +2038,26 @@ export function lintStyleWiring(manifest, files) {
|
|
|
2033
2038
|
return { ok: !hasErrors, findings };
|
|
2034
2039
|
}
|
|
2035
2040
|
|
|
2041
|
+
const _BASIC_STYLE_CALL = /\bbasicStyle\s*\(/;
|
|
2042
|
+
|
|
2043
|
+
const _BASIC_STYLE_FIX =
|
|
2044
|
+
` The one-line fix for every basic field: basicStyle(props.style) from ` +
|
|
2045
|
+
`@colixsystems/widget-sdk — spread its .root on the outermost View and ` +
|
|
2046
|
+
`its .text on each Text in a theme text colour.`;
|
|
2047
|
+
|
|
2048
|
+
/**
|
|
2049
|
+
* Whether the bundle calls `basicStyle()`, which applies every basic style
|
|
2050
|
+
* field (`CONTRACT.basicStyleSchema`) in one call.
|
|
2051
|
+
*
|
|
2052
|
+
* @param {object|string[]|string} files bundle sources, as `lintStyleWiring` takes them
|
|
2053
|
+
* @returns {boolean}
|
|
2054
|
+
*/
|
|
2055
|
+
export function callsBasicStyle(files) {
|
|
2056
|
+
return _bundleSources(files).some((s) =>
|
|
2057
|
+
_BASIC_STYLE_CALL.test(_stripNonCode(s)),
|
|
2058
|
+
);
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2036
2061
|
// sc-7799 — a group name that groups everything defeats the rule: one
|
|
2037
2062
|
// catch-all fieldset reads exactly like the flat list it replaced.
|
|
2038
2063
|
const _GENERIC_STYLE_GROUPS = new Set([
|
|
@@ -2045,16 +2070,9 @@ const _GENERIC_STYLE_GROUPS = new Set([
|
|
|
2045
2070
|
]);
|
|
2046
2071
|
|
|
2047
2072
|
// The five the basic-style gate mandates; they restyle the widget as a whole.
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
const _WHOLE_WIDGET_STYLE_FIELDS = new Set([
|
|
2052
|
-
"background",
|
|
2053
|
-
"textColor",
|
|
2054
|
-
"align",
|
|
2055
|
-
"padding",
|
|
2056
|
-
"margin",
|
|
2057
|
-
]);
|
|
2073
|
+
const _WHOLE_WIDGET_STYLE_FIELDS = new Set(
|
|
2074
|
+
Object.keys(CONTRACT.basicStyleSchema),
|
|
2075
|
+
);
|
|
2058
2076
|
|
|
2059
2077
|
// A second element an author would style apart from the text. Pressable and
|
|
2060
2078
|
// Button are deliberately absent: a touch target wrapping the only label is
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.271.0",
|
|
4
4
|
"description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
|
|
5
5
|
"homepage": "https://github.com/Colix-AB/AppStudio",
|
|
6
6
|
"type": "module",
|
package/src/contract.js
CHANGED
|
@@ -386,6 +386,38 @@ const THEME_SPACING_SCALE = Object.freeze({
|
|
|
386
386
|
default: 1,
|
|
387
387
|
});
|
|
388
388
|
|
|
389
|
+
// The style fields every generated widget declares, with the controls the
|
|
390
|
+
// Studio renders for them. One source for `BASIC_STYLE_SCHEMA` /
|
|
391
|
+
// `basicStyle()`, the linter, the publish gate and the Mason plan gate.
|
|
392
|
+
const BASIC_STYLE_GROUP = Object.freeze({ group: "Basics" });
|
|
393
|
+
const BASIC_SPACING_RANGE = Object.freeze({ min: 0, max: 64 });
|
|
394
|
+
const BASIC_STYLE_SCHEMA = Object.freeze({
|
|
395
|
+
background: Object.freeze({ type: "color", label: "Background", ui: BASIC_STYLE_GROUP }),
|
|
396
|
+
textColor: Object.freeze({ type: "color", label: "Text color", ui: BASIC_STYLE_GROUP }),
|
|
397
|
+
align: Object.freeze({
|
|
398
|
+
type: "select",
|
|
399
|
+
label: "Align",
|
|
400
|
+
enum: Object.freeze([
|
|
401
|
+
Object.freeze({ value: "left", label: "Left" }),
|
|
402
|
+
Object.freeze({ value: "center", label: "Center" }),
|
|
403
|
+
Object.freeze({ value: "right", label: "Right" }),
|
|
404
|
+
]),
|
|
405
|
+
ui: BASIC_STYLE_GROUP,
|
|
406
|
+
}),
|
|
407
|
+
padding: Object.freeze({
|
|
408
|
+
type: "spacing",
|
|
409
|
+
label: "Padding",
|
|
410
|
+
validation: BASIC_SPACING_RANGE,
|
|
411
|
+
ui: BASIC_STYLE_GROUP,
|
|
412
|
+
}),
|
|
413
|
+
margin: Object.freeze({
|
|
414
|
+
type: "spacing",
|
|
415
|
+
label: "Margin",
|
|
416
|
+
validation: BASIC_SPACING_RANGE,
|
|
417
|
+
ui: BASIC_STYLE_GROUP,
|
|
418
|
+
}),
|
|
419
|
+
});
|
|
420
|
+
|
|
389
421
|
// The card-surface field names shared by every widget that paints its own card
|
|
390
422
|
// (frontend/src/components/widgets/_shared/cardStyle.js CARD_STYLE_SCHEMA).
|
|
391
423
|
const CARD_SURFACE_FIELDS = Object.freeze({
|
|
@@ -6505,7 +6537,9 @@ const CONTRACT = deepFreeze({
|
|
|
6505
6537
|
// every query, record, figure and aggregate on that table, app-wide.
|
|
6506
6538
|
// 1.220.0: additive (sc-14423) — `live: true` on useDatastoreQuery,
|
|
6507
6539
|
// useDatastoreRecord, useDatastoreFigure and useDatastoreAggregate.
|
|
6508
|
-
|
|
6540
|
+
// 1.221.0: additive (sc-14460) — `basicStyleSchema`, exported as
|
|
6541
|
+
// `BASIC_STYLE_SCHEMA`, and `basicStyle(style)`, which applies it.
|
|
6542
|
+
version: "1.221.0",
|
|
6509
6543
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6510
6544
|
hooks: HOOKS,
|
|
6511
6545
|
primitives: PRIMITIVES,
|
|
@@ -6530,6 +6564,7 @@ const CONTRACT = deepFreeze({
|
|
|
6530
6564
|
themeSpacingScale: THEME_SPACING_SCALE,
|
|
6531
6565
|
themeMenuTypes: THEME_MENU_TYPES,
|
|
6532
6566
|
themeWidgetStyles: THEME_WIDGET_STYLES,
|
|
6567
|
+
basicStyleSchema: BASIC_STYLE_SCHEMA,
|
|
6533
6568
|
widgetContextShape: WIDGET_CONTEXT_SHAPE,
|
|
6534
6569
|
bundleExportContract: BUNDLE_EXPORT_CONTRACT,
|
|
6535
6570
|
// sc-10829 — the hook a `badgeCounter: true` entry exports by name.
|