@multiplatform.one/config 7.43.0 → 7.45.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
@@ -143,8 +143,13 @@ the enclosing `Svg` has a `viewBox`, including one passed through a spread, or
143
143
  when the mark is a fragment drawn into an `Svg` elsewhere. Primitives inside an
144
144
  `Svg` with no `viewBox`, and the `Svg` element's own size, are still reported.
145
145
 
146
- Every violation is tagged with its rule: `hex`, `geometry`, `inline-spacing`,
147
- `raw-control`, `styled-mpo` or `token-file`.
146
+ A `focusStyle`, `focusVisibleStyle` or `focusWithinStyle` that paints its own
147
+ outline, box shadow, shadow or elevation is reported as `field-focus`. Focus
148
+ looks come from MPO: fields take `FIELD_FOCUS_STYLE` (the accent border), other
149
+ controls the theme's focus ring. Zeroing those keys is allowed.
150
+
151
+ Every violation is tagged with its rule: `field-focus`, `hex`, `geometry`,
152
+ `inline-spacing`, `raw-control`, `styled-mpo` or `token-file`.
148
153
 
149
154
  #### Ratchet
150
155
 
package/design-lint.mjs CHANGED
@@ -51,6 +51,11 @@ const svgPrimitives = new Set([
51
51
  "Use",
52
52
  ]);
53
53
  const svgRoots = new Set(["Svg", "Symbol"]);
54
+ // FZ-7717: focus looks come from MPO (FIELD_FOCUS_STYLE / the control ring).
55
+ // A use site that paints its own focus ring or shadow forks the look.
56
+ const focusPaint =
57
+ /^(?:outline(?:Width|Color|Offset)?|boxShadow|shadow(?:Color|Opacity|Radius|Offset)|elevation)$/;
58
+ const focusStates = new Set(["focusStyle", "focusVisibleStyle", "focusWithinStyle"]);
54
59
  const tokenFactories = new Set([
55
60
  "createTokens",
56
61
  "createTamagui",
@@ -59,6 +64,7 @@ const tokenFactories = new Set([
59
64
  "createFont",
60
65
  ]);
61
66
  export const designRules = [
67
+ "field-focus",
62
68
  "hex",
63
69
  "geometry",
64
70
  "inline-spacing",
@@ -224,6 +230,42 @@ export function inspectDesign(source, filename, projectRoot = process.cwd()) {
224
230
  return spacing(value.whenTrue) || spacing(value.whenFalse);
225
231
  return false;
226
232
  }
233
+ function inert(node) {
234
+ const value = resolve(node);
235
+ if (ts.isNumericLiteral(value)) return Number(value.text) === 0;
236
+ if (ts.isStringLiteralLike(value)) return /^(?:0(?:px)?|none|transparent)$/.test(value.text);
237
+ return value.kind === ts.SyntaxKind.UndefinedKeyword || value.getText(file) === "undefined";
238
+ }
239
+ function paintsFocus(node, seen = new Set()) {
240
+ const value = resolve(node);
241
+ if (seen.has(value)) return undefined;
242
+ seen.add(value);
243
+ if (ts.isConditionalExpression(value))
244
+ return paintsFocus(value.whenTrue, seen) ?? paintsFocus(value.whenFalse, seen);
245
+ if (!ts.isObjectLiteralExpression(value)) return undefined;
246
+ for (const prop of value.properties) {
247
+ if (ts.isSpreadAssignment(prop)) {
248
+ const inner = paintsFocus(prop.expression, seen);
249
+ if (inner) return inner;
250
+ } else if (
251
+ ts.isPropertyAssignment(prop) &&
252
+ focusPaint.test(name(prop.name)) &&
253
+ !inert(prop.initializer)
254
+ )
255
+ return prop;
256
+ }
257
+ return undefined;
258
+ }
259
+ function checkFocus(key, value, node) {
260
+ if (!focusStates.has(key)) return;
261
+ const painted = paintsFocus(value);
262
+ if (painted)
263
+ report(
264
+ node,
265
+ "field-focus",
266
+ `${key} paints its own ${name(painted.name)}; focus comes from MPO (FIELD_FOCUS_STYLE).`,
267
+ );
268
+ }
227
269
  function report(node, rule, message) {
228
270
  const { line, character } = file.getLineAndCharacterOfPosition(node.getStart(file));
229
271
  errors.push(`${filename}:${line + 1}:${character + 1}: [${rule}] ${message}`);
@@ -260,6 +302,7 @@ export function inspectDesign(source, filename, projectRoot = process.cwd()) {
260
302
  : prop.initializer;
261
303
  if (!initializer) return;
262
304
  const key = name(prop.name);
305
+ checkFocus(key, initializer, prop);
263
306
  if (layout.test(key) && numeric(initializer))
264
307
  report(prop, "geometry", `${name(prop.name)} requires a token, not a numeric literal.`);
265
308
  if (/style$/i.test(key) && spacing(initializer))
@@ -346,6 +389,7 @@ export function inspectDesign(source, filename, projectRoot = process.cwd()) {
346
389
  const value = ts.isJsxExpression(node.initializer)
347
390
  ? node.initializer.expression
348
391
  : node.initializer;
392
+ if (value) checkFocus(key, value, node);
349
393
  if (value && (key.startsWith("$") || /style$/i.test(key))) inspectProps(value);
350
394
  if (value && layout.test(key) && numeric(value) && !svgArtwork(node.parent.parent))
351
395
  report(node, "geometry", `${key} requires a token, not a numeric literal.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiplatform.one/config",
3
- "version": "7.43.0",
3
+ "version": "7.45.0",
4
4
  "description": "Shared build and test configuration presets for multiplatform.one",
5
5
  "keywords": [
6
6
  "config",
@@ -106,7 +106,7 @@
106
106
  "access": "public"
107
107
  },
108
108
  "dependencies": {
109
- "@multiplatform.one/utils": "^7.43.0",
109
+ "@multiplatform.one/utils": "^7.45.0",
110
110
  "@tamagui/vite-plugin": "2.7.6",
111
111
  "@vitejs/plugin-react": "^6.1.2",
112
112
  "dotenv": "^17.4.2",