@godxjp/ui 33.7.0 → 33.7.2

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@godxjp/ui",
3
- "version": "33.7.0",
4
- "godxUiMcp": "33.7.0",
3
+ "version": "33.7.2",
4
+ "godxUiMcp": "33.7.2",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
@@ -235,7 +235,8 @@ const PALETTE =
235
235
  * Followed by U+FE0F they are asking for emoji presentation on purpose, and then they ARE emoji —
236
236
  * so that spelling is still caught.
237
237
  */
238
- const EMOJI = /\p{Extended_Pictographic}\uFE0F|(?=\p{Extended_Pictographic})\p{Emoji_Presentation}/u;
238
+ const EMOJI =
239
+ /\p{Extended_Pictographic}\uFE0F|(?=\p{Extended_Pictographic})\p{Emoji_Presentation}/u;
239
240
  /** Regional-indicator pairs = emoji flags (🇯🇵) — broken on Win/Linux; use Intl.DisplayNames. */
240
241
  const EMOJI_FLAG = /\p{Regional_Indicator}/u;
241
242
 
@@ -995,7 +996,9 @@ const RULES = [
995
996
  scope: "consumer",
996
997
  severity: "warn",
997
998
  spansElement: true,
998
- matches: (source) => densityMatches(source, ["Card"], CARD_LIMIT),
999
+ // A dashboard's tiles ARE its surfaces — the same path test kpi-row-off-dashboard uses.
1000
+ matches: (source, rel = "") =>
1001
+ /dashboard/i.test(rel) ? [] : densityMatches(source, ["Card"], CARD_LIMIT),
999
1002
  message: `${CARD_LIMIT}+ Cards in one file — a Card per section. Mark sections with a Heading and section spacing (PageContainer children, Flex gap="lg"); keep Card for a real boundary (a table surface, a selectable item).`,
1000
1003
  },
1001
1004
  {
@@ -1055,7 +1058,8 @@ const RULES = [
1055
1058
  scope: "consumer",
1056
1059
  severity: "warn",
1057
1060
  spansElement: true,
1058
- matches: (source) => densityMatches(source, ["Alert"], ALERT_LIMIT),
1061
+ // An Alert behind a condition IS a state (what the message asks Alerts to be); count the standing ones.
1062
+ matches: (source) => densityMatches(source, ["Alert"], ALERT_LIMIT, { skipConditional: true }),
1059
1063
  message: `${ALERT_LIMIT}+ Alerts in one file. An Alert is for a state that blocks or changes the task; field errors belong in FormField, results in a toast, and standing info in plain text.`,
1060
1064
  },
1061
1065
  ];
@@ -1810,9 +1814,45 @@ function* mixedButtonSizeMatches(source) {
1810
1814
  }
1811
1815
 
1812
1816
  /** One finding per file, at the `limit`-th occurrence of any tag in `tags`, naming the count. */
1813
- function* densityMatches(source, tags, limit) {
1817
+ /**
1818
+ * The JS token before a JSX element, as the expression that places it: `?` / `:` (a ternary
1819
+ * branch), `&&` / `||` (a guard), `=>` (a callback, e.g. `.map`), `return`, or "" when the element
1820
+ * is a plain JSX child. An opening `(` is skipped — `cond ? (\n <Alert` reads as `?`.
1821
+ */
1822
+ function placementBefore(source, index) {
1823
+ let i = index - 1;
1824
+ while (i >= 0 && (/\s/.test(source[i]) || source[i] === "(")) i -= 1;
1825
+ if (source[i] === "?" || source[i] === ":") return source[i];
1826
+ if (source.slice(i - 1, i + 1) === "&&" || source.slice(i - 1, i + 1) === "||") return "&&";
1827
+ if (source.slice(i - 1, i + 1) === "=>") return "=>";
1828
+ if (/\breturn$/.test(source.slice(Math.max(0, i - 5), i + 1))) return "return";
1829
+ return "";
1830
+ }
1831
+
1832
+ /**
1833
+ * ≥limit elements of `tags` that can be on screen TOGETHER. Mutually exclusive branches of one state
1834
+ * count once: the else branch of a ternary (`a ? <X/> : <X/>`) is the same slot as its `?` branch,
1835
+ * and a function that returns the element from several `if`s returns ONE of them — gino-cloud's
1836
+ * 実習生詳細 had 6 Badge tags and showed at most 3. `skipConditional` drops elements behind a
1837
+ * condition altogether (a guard, a ternary, a `.map`).
1838
+ */
1839
+ function* densityMatches(source, tags, limit, { skipConditional = false } = {}) {
1814
1840
  const hits = [];
1815
- for (const { tag, index } of jsxElements(source)) if (tags.includes(tag)) hits.push(index);
1841
+ const returnedIn = new Set(); // function starts that already returned one of `tags`
1842
+ for (const { tag, index } of jsxElements(source)) {
1843
+ if (!tags.includes(tag)) continue;
1844
+ const how = placementBefore(source, index);
1845
+ if (skipConditional && (how === "?" || how === ":" || how === "&&" || how === "=>")) continue;
1846
+ if (how === ":") continue;
1847
+ if (how === "return" || how === "?") {
1848
+ const head = source.slice(0, index);
1849
+ const arrows = [...head.matchAll(/=>\s*\{/g)];
1850
+ const fn = Math.max(head.lastIndexOf("function "), arrows.at(-1)?.index ?? -1);
1851
+ if (returnedIn.has(fn)) continue;
1852
+ returnedIn.add(fn);
1853
+ }
1854
+ hits.push(index);
1855
+ }
1816
1856
  if (hits.length >= limit) yield { 0: `${hits.length} in this file`, index: hits[limit - 1] };
1817
1857
  }
1818
1858