@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 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.270.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**.
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
- version: "1.220.0",
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
- version: "1.220.0",
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 {
@@ -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
- // Lockstep with BASIC_STYLE_FIELDS (backend/src/core/services/widget-basic-style.ts)
1986
- // — a mandated field missing here reads as per-element coverage the widget does
1987
- // not have, which silently retires this rule for EVERY generated widget.
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
- // Lockstep with BASIC_STYLE_FIELDS (backend/src/core/services/widget-basic-style.ts)
2049
- // — a mandated field missing here reads as per-element coverage the widget does
2050
- // not have, which silently retires this rule for EVERY generated widget.
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.270.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
- version: "1.220.0",
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.