mac-human-design 0.1.30-preview.1 → 0.1.30-preview.3

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.
Files changed (39) hide show
  1. package/README.md +2 -2
  2. package/changelog.md +17 -0
  3. package/docs/macos-base-ui-coverage.md +67 -67
  4. package/examples/macos-base-ui-gallery/src/main.tsx +6 -6
  5. package/package.json +11 -2
  6. package/scripts/verify-base-ui-coverage.mjs +96 -109
  7. package/scripts/verify-macos-design-components.mjs +224 -0
  8. package/scripts/verify-macos-design-icons.mjs +86 -0
  9. package/scripts/verify-macos-design.mjs +109 -327
  10. package/src/components/AppWindowShell.tsx +3 -5
  11. package/src/components/MacBaseUI.tsx +3 -736
  12. package/src/components/MacBaseUIControls.tsx +202 -0
  13. package/src/components/MacBaseUIGallery.tsx +14 -456
  14. package/src/components/MacBaseUIGalleryAdvanced.tsx +294 -0
  15. package/src/components/MacBaseUIGalleryControls.tsx +196 -0
  16. package/src/components/MacBaseUIGalleryShared.tsx +21 -0
  17. package/src/components/MacBaseUIInternals.tsx +297 -0
  18. package/src/components/MacBaseUINavigation.tsx +233 -0
  19. package/src/components/MacDataTable.tsx +95 -53
  20. package/src/components/MacSegmentedControl.tsx +22 -26
  21. package/src/components/MacWindowToolbar.tsx +17 -17
  22. package/src/components/StatusMessage.tsx +5 -5
  23. package/src/components/SymbolIconButton.tsx +30 -30
  24. package/src/components/ViewDragRegion.tsx +3 -3
  25. package/src/components/index.ts +9 -9
  26. package/src/index.ts +14 -14
  27. package/src/styles/macosBaseUi.css +67 -29
  28. package/src/styles/macosTauri.css +2 -4
  29. package/src/symbols/SystemSymbolImage.tsx +20 -20
  30. package/src/symbols/index.ts +3 -3
  31. package/src/symbols/systemSymbolService.ts +71 -89
  32. package/src/symbols/useSystemSymbol.ts +27 -32
  33. package/src/tauri/index.ts +2 -4
  34. package/src/tauri/macosWindowConfig.ts +3 -3
  35. package/src/theme/index.ts +1 -1
  36. package/src/theme/macosTheme.ts +9 -9
  37. package/src/types/css.d.ts +1 -1
  38. package/src/utils/index.ts +1 -1
  39. package/src/utils/isDraggableAreaTarget.ts +4 -7
package/README.md CHANGED
@@ -31,7 +31,7 @@ Consumer apps should also include the macOS Base UI stylesheet once near their
31
31
  app root:
32
32
 
33
33
  ```ts
34
- import "mac-human-design/styles/macos-base-ui.css";
34
+ import "mac-human-design/styles/macos-base-ui.css"
35
35
  ```
36
36
 
37
37
  In the Tauri Rust side, add the local path dependency while developing:
@@ -172,5 +172,5 @@ import {
172
172
  SymbolIconButton,
173
173
  ViewDragRegion,
174
174
  isDraggableAreaTarget,
175
- } from "mac-human-design";
175
+ } from "mac-human-design"
176
176
  ```
package/changelog.md CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  This file tracks changes between published npm versions of `mac-human-design`.
4
4
 
5
+ ## 0.1.30-preview.2
6
+
7
+ ### Added
8
+
9
+ - Added `MacDataTable` arrow, Home, and End keyboard navigation for interactive
10
+ rows.
11
+ - Added a layered `MacDataTable` row hover and selection treatment so row
12
+ feedback animates under cell content without shifting text or layout.
13
+ - Added verifier coverage for data-table keyboard navigation and row state
14
+ layer invariants.
15
+
16
+ ### Changed
17
+
18
+ - Improved Finder-style table interaction polish for object-browser style
19
+ screens while preserving row geometry and selected-row focus rings.
20
+ - Bumped the package to `0.1.30-preview.2` under the `preview` dist-tag.
21
+
5
22
  ## 0.1.30-preview.1
6
23
 
7
24
  ### Added
@@ -4,7 +4,7 @@
4
4
  1.5.0 with macOS-oriented class names. Import the CSS once:
5
5
 
6
6
  ```ts
7
- import "mac-human-design/styles/macos-base-ui.css";
7
+ import "mac-human-design/styles/macos-base-ui.css"
8
8
  ```
9
9
 
10
10
  Consumer apps install `@base-ui/react`, `motion`, `react`, and `react-dom` as
@@ -14,48 +14,48 @@ preference through `MacMotionProvider`.
14
14
 
15
15
  ## Component Families
16
16
 
17
- | Base UI family | macOS export |
18
- | --- | --- |
19
- | `accordion` | `MacAccordion` |
20
- | `alert-dialog` | `MacAlertDialog` |
21
- | `autocomplete` | `MacAutocomplete` |
22
- | `avatar` | `MacAvatar` |
23
- | `button` | `MacButton` |
24
- | `checkbox` | `MacCheckbox` |
25
- | `checkbox-group` | `MacCheckboxGroup` |
26
- | `collapsible` | `MacCollapsible` |
27
- | `combobox` | `MacCombobox` |
28
- | `context-menu` | `MacContextMenu` |
29
- | `csp-provider` | `MacCSPProvider` |
30
- | `dialog` | `MacDialog` |
17
+ | Base UI family | macOS export |
18
+ | -------------------- | ---------------------- |
19
+ | `accordion` | `MacAccordion` |
20
+ | `alert-dialog` | `MacAlertDialog` |
21
+ | `autocomplete` | `MacAutocomplete` |
22
+ | `avatar` | `MacAvatar` |
23
+ | `button` | `MacButton` |
24
+ | `checkbox` | `MacCheckbox` |
25
+ | `checkbox-group` | `MacCheckboxGroup` |
26
+ | `collapsible` | `MacCollapsible` |
27
+ | `combobox` | `MacCombobox` |
28
+ | `context-menu` | `MacContextMenu` |
29
+ | `csp-provider` | `MacCSPProvider` |
30
+ | `dialog` | `MacDialog` |
31
31
  | `direction-provider` | `MacDirectionProvider` |
32
- | `drawer` | `MacDrawer` |
33
- | `field` | `MacField` |
34
- | `fieldset` | `MacFieldset` |
35
- | `form` | `MacForm` |
36
- | `input` | `MacInput` |
37
- | `menu` | `MacMenu` |
38
- | `menubar` | `MacMenubar` |
39
- | `meter` | `MacMeter` |
40
- | `navigation-menu` | `MacNavigationMenu` |
41
- | `number-field` | `MacNumberField` |
42
- | `otp-field` | `MacOTPField` |
43
- | `popover` | `MacPopover` |
44
- | `preview-card` | `MacPreviewCard` |
45
- | `progress` | `MacProgress` |
46
- | `radio` | `MacRadio` |
47
- | `radio-group` | `MacRadioGroup` |
48
- | `scroll-area` | `MacScrollArea` |
49
- | `select` | `MacSelect` |
50
- | `separator` | `MacSeparator` |
51
- | `slider` | `MacSlider` |
52
- | `switch` | `MacSwitch` |
53
- | `tabs` | `MacTabs` |
54
- | `toast` | `MacToast` |
55
- | `toggle` | `MacToggle` |
56
- | `toggle-group` | `MacToggleGroup` |
57
- | `toolbar` | `MacToolbar` |
58
- | `tooltip` | `MacTooltip` |
32
+ | `drawer` | `MacDrawer` |
33
+ | `field` | `MacField` |
34
+ | `fieldset` | `MacFieldset` |
35
+ | `form` | `MacForm` |
36
+ | `input` | `MacInput` |
37
+ | `menu` | `MacMenu` |
38
+ | `menubar` | `MacMenubar` |
39
+ | `meter` | `MacMeter` |
40
+ | `navigation-menu` | `MacNavigationMenu` |
41
+ | `number-field` | `MacNumberField` |
42
+ | `otp-field` | `MacOTPField` |
43
+ | `popover` | `MacPopover` |
44
+ | `preview-card` | `MacPreviewCard` |
45
+ | `progress` | `MacProgress` |
46
+ | `radio` | `MacRadio` |
47
+ | `radio-group` | `MacRadioGroup` |
48
+ | `scroll-area` | `MacScrollArea` |
49
+ | `select` | `MacSelect` |
50
+ | `separator` | `MacSeparator` |
51
+ | `slider` | `MacSlider` |
52
+ | `switch` | `MacSwitch` |
53
+ | `tabs` | `MacTabs` |
54
+ | `toast` | `MacToast` |
55
+ | `toggle` | `MacToggle` |
56
+ | `toggle-group` | `MacToggleGroup` |
57
+ | `toolbar` | `MacToolbar` |
58
+ | `tooltip` | `MacTooltip` |
59
59
 
60
60
  Support exports such as `merge-props`, `use-render`, and
61
61
  `unstable-use-media-query` are intentionally not wrapped because they do not
@@ -63,38 +63,38 @@ render UI.
63
63
 
64
64
  ## Native Variants
65
65
 
66
- | Native role | Export |
67
- | --- | --- |
68
- | Default push button | `MacButton` |
69
- | Blue default button | `MacPrimaryButton` |
70
- | Secondary push button | `MacSecondaryButton` |
71
- | Destructive button | `MacDestructiveButton` |
72
- | Borderless button | `MacPlainButton` |
73
- | Icon button | `MacIconButton` |
74
- | Help button | `MacHelpButton` |
75
- | Motion defaults provider | `MacMotionProvider` |
76
- | Text field | `MacTextField` / `MacInput` |
77
- | Search field | `MacSearchField` |
78
- | Pop-up button | `MacSelect.PopUpButtonTrigger` |
79
- | Pull-down button | `MacSelect.PullDownButtonTrigger` |
80
- | Segmented control | `MacSegmentedToggleGroup` |
66
+ | Native role | Export |
67
+ | --------------------------- | ---------------------------------- |
68
+ | Default push button | `MacButton` |
69
+ | Blue default button | `MacPrimaryButton` |
70
+ | Secondary push button | `MacSecondaryButton` |
71
+ | Destructive button | `MacDestructiveButton` |
72
+ | Borderless button | `MacPlainButton` |
73
+ | Icon button | `MacIconButton` |
74
+ | Help button | `MacHelpButton` |
75
+ | Motion defaults provider | `MacMotionProvider` |
76
+ | Text field | `MacTextField` / `MacInput` |
77
+ | Search field | `MacSearchField` |
78
+ | Pop-up button | `MacSelect.PopUpButtonTrigger` |
79
+ | Pull-down button | `MacSelect.PullDownButtonTrigger` |
80
+ | Segmented control | `MacSegmentedToggleGroup` |
81
81
  | Separated segmented control | `MacSeparatedSegmentedToggleGroup` |
82
- | Toolbar toggle | `MacToolbarToggle` |
83
- | Toolbar icon button | `MacToolbar.IconButton` |
84
- | Window sheet | `MacDialog.SheetPopup` |
85
- | Sidebar drawer | `MacDrawer.SidebarPopup` |
82
+ | Toolbar toggle | `MacToolbarToggle` |
83
+ | Toolbar icon button | `MacToolbar.IconButton` |
84
+ | Window sheet | `MacDialog.SheetPopup` |
85
+ | Sidebar drawer | `MacDrawer.SidebarPopup` |
86
86
 
87
87
  ## App-Level Components
88
88
 
89
89
  These shared components cover reusable macOS/Tauri app patterns that should not
90
90
  be reimplemented in consuming apps:
91
91
 
92
- | App pattern | Export | Contract |
93
- | --- | --- | --- |
94
- | Finder-style grid/table primitive | `MacDataTable` | Uses `table` semantics for static rows, upgrades to `grid` semantics for interactive rows, exposes row/column metadata, busy state, keyboard activation, selected row state, and focus-visible row styling. |
95
- | Tauri window toolbar | `MacWindowToolbar` | Provides a labelled `toolbar` landmark, labelled leading/content/trailing control groups, a root drag region, no-drag regions around interactive controls, and stable wrapped-control sizing for dense icon toolbars. |
96
- | Segmented control | `MacSegmentedControl` | Wraps the shared macOS segmented toggle styling with accessible labels, stable class names, full-width layout support, and class/style pass-through. |
97
- | Centered SF Symbols icon button | `SymbolIconButton` | Renders through `MacIconButton`, defaults to `type="button"`, preserves fixed geometry while allowing style/class pass-through, supports tooltip offsets and per-symbol optical offsets, and exposes loading state with `aria-busy`. |
92
+ | App pattern | Export | Contract |
93
+ | --------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
94
+ | Finder-style grid/table primitive | `MacDataTable` | Uses `table` semantics for static rows, upgrades to `grid` semantics for interactive rows, exposes row/column metadata, busy state, keyboard activation, arrow/Home/End row focus navigation, selected row state, and layered hover/selection styling. |
95
+ | Tauri window toolbar | `MacWindowToolbar` | Provides a labelled `toolbar` landmark, labelled leading/content/trailing control groups, a root drag region, no-drag regions around interactive controls, and stable wrapped-control sizing for dense icon toolbars. |
96
+ | Segmented control | `MacSegmentedControl` | Wraps the shared macOS segmented toggle styling with accessible labels, stable class names, full-width layout support, and class/style pass-through. |
97
+ | Centered SF Symbols icon button | `SymbolIconButton` | Renders through `MacIconButton`, defaults to `type="button"`, preserves fixed geometry while allowing style/class pass-through, supports tooltip offsets and per-symbol optical offsets, and exposes loading state with `aria-busy`. |
98
98
 
99
99
  ## Icon Button Alignment Guarantees
100
100
 
@@ -1,8 +1,8 @@
1
- import { StrictMode } from "react";
2
- import { createRoot } from "react-dom/client";
3
- import { AppWindowShell, MacBaseUIGallery } from "../../../src";
4
- import "../../../src/styles/macosBaseUi.css";
5
- import "../../../src/styles/macosTauri.css";
1
+ import { StrictMode } from "react"
2
+ import { createRoot } from "react-dom/client"
3
+ import { AppWindowShell, MacBaseUIGallery } from "../../../src"
4
+ import "../../../src/styles/macosBaseUi.css"
5
+ import "../../../src/styles/macosTauri.css"
6
6
 
7
7
  createRoot(document.getElementById("root") as HTMLElement).render(
8
8
  <StrictMode>
@@ -10,4 +10,4 @@ createRoot(document.getElementById("root") as HTMLElement).render(
10
10
  <MacBaseUIGallery />
11
11
  </AppWindowShell>
12
12
  </StrictMode>,
13
- );
13
+ )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mac-human-design",
3
- "version": "0.1.30-preview.1",
3
+ "version": "0.1.30-preview.3",
4
4
  "description": "Reusable macOS-oriented UI primitives and SF Symbols bridge for Tauri apps.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -36,8 +36,11 @@
36
36
  "components"
37
37
  ],
38
38
  "scripts": {
39
- "check": "npm run typecheck && npm run verify:base-ui && npm run verify:design && npm run verify:gallery",
39
+ "check": "npm run lint && npm run format:check && npm run typecheck && npm run verify:base-ui && npm run verify:design && npm run verify:gallery",
40
40
  "dev:gallery": "vite --host 127.0.0.1 examples/macos-base-ui-gallery",
41
+ "format": "prettier --write .",
42
+ "format:check": "prettier --check .",
43
+ "lint": "eslint . --max-warnings=0",
41
44
  "publish": "npm publish --access public --ignore-scripts",
42
45
  "publish:preview": "npm publish --access public --ignore-scripts --tag preview",
43
46
  "typecheck": "tsc --noEmit",
@@ -61,14 +64,20 @@
61
64
  },
62
65
  "devDependencies": {
63
66
  "@base-ui/react": "^1.5.0",
67
+ "@eslint/js": "^10.0.1",
64
68
  "@tauri-apps/api": "^2.11.0",
65
69
  "@types/react": "^19.2.17",
66
70
  "@types/react-dom": "^19.2.3",
67
71
  "@vitejs/plugin-react": "^6.0.2",
72
+ "eslint": "^10.4.1",
73
+ "eslint-plugin-react-hooks": "^7.1.1",
74
+ "globals": "^17.6.0",
68
75
  "motion": "^12.40.0",
76
+ "prettier": "^3.8.4",
69
77
  "react": "^19.2.7",
70
78
  "react-dom": "^19.2.7",
71
79
  "typescript": "^6.0.3",
80
+ "typescript-eslint": "^8.61.0",
72
81
  "vite": "^8.0.16"
73
82
  }
74
83
  }
@@ -1,162 +1,156 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
-
5
- const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
6
- const packageJsonPath = path.join(repoRoot, "node_modules", "@base-ui", "react", "package.json");
7
- const wrapperPath = path.join(repoRoot, "src", "components", "MacBaseUI.tsx");
8
- const galleryPath = path.join(repoRoot, "src", "components", "MacBaseUIGallery.tsx");
9
- const stylesPath = path.join(repoRoot, "src", "styles", "macosBaseUi.css");
10
- const docsPath = path.join(repoRoot, "docs", "macos-base-ui-coverage.md");
11
-
12
- const nonComponentExports = new Set([
13
- ".",
14
- "esm",
15
- "merge-props",
16
- "types",
17
- "unstable-use-media-query",
18
- "use-render",
19
- ]);
1
+ import fs from "node:fs"
2
+ import path from "node:path"
3
+ import { fileURLToPath } from "node:url"
4
+
5
+ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
6
+ const packageJsonPath = path.join(repoRoot, "node_modules", "@base-ui", "react", "package.json")
7
+ const wrapperPaths = [
8
+ path.join(repoRoot, "src", "components", "MacBaseUI.tsx"),
9
+ path.join(repoRoot, "src", "components", "MacBaseUIInternals.tsx"),
10
+ path.join(repoRoot, "src", "components", "MacBaseUIControls.tsx"),
11
+ path.join(repoRoot, "src", "components", "MacBaseUINavigation.tsx"),
12
+ ]
13
+ const stylesPath = path.join(repoRoot, "src", "styles", "macosBaseUi.css")
14
+ const docsPath = path.join(repoRoot, "docs", "macos-base-ui-coverage.md")
15
+
16
+ const nonComponentExports = new Set([".", "esm", "merge-props", "types", "unstable-use-media-query", "use-render"])
20
17
 
21
18
  const acronymParts = new Map([
22
19
  ["csp", "CSP"],
23
20
  ["otp", "OTP"],
24
- ]);
21
+ ])
25
22
 
26
23
  function readText(filePath) {
27
- return fs.readFileSync(filePath, "utf8");
24
+ return fs.readFileSync(filePath, "utf8")
28
25
  }
29
26
 
30
27
  function toMacExportName(family) {
31
28
  return `Mac${family
32
29
  .split("-")
33
30
  .map((part) => acronymParts.get(part) ?? `${part[0].toUpperCase()}${part.slice(1)}`)
34
- .join("")}`;
31
+ .join("")}`
35
32
  }
36
33
 
37
34
  function getBaseUiFamilies() {
38
- const packageJson = JSON.parse(readText(packageJsonPath));
35
+ const packageJson = JSON.parse(readText(packageJsonPath))
39
36
  return Object.keys(packageJson.exports)
40
37
  .filter((exportKey) => exportKey.startsWith("./"))
41
38
  .map((exportKey) => exportKey.slice(2))
42
39
  .filter((exportName) => !exportName.startsWith("internals/"))
43
40
  .filter((exportName) => exportName !== "package.json")
44
41
  .filter((exportName) => !nonComponentExports.has(exportName))
45
- .sort();
42
+ .sort()
46
43
  }
47
44
 
48
45
  function getMacExports(source) {
49
- return new Set(
50
- Array.from(
51
- source.matchAll(/^export (?:const|function) (Mac[A-Za-z0-9]+)\b/gm),
52
- (match) => match[1],
53
- ),
54
- );
46
+ return new Set(Array.from(source.matchAll(/^export (?:const|function) (Mac[A-Za-z0-9]+)\b/gm), (match) => match[1]))
55
47
  }
56
48
 
57
49
  function getBaseUiParts(family) {
58
- const partsPath = path.join(repoRoot, "node_modules", "@base-ui", "react", family, "index.parts.d.ts");
50
+ const partsPath = path.join(repoRoot, "node_modules", "@base-ui", "react", family, "index.parts.d.ts")
59
51
  if (!fs.existsSync(partsPath)) {
60
- return [];
52
+ return []
61
53
  }
62
54
 
63
- const partsSource = readText(partsPath);
64
- const parts = new Set();
55
+ const partsSource = readText(partsPath)
56
+ const parts = new Set()
65
57
 
66
58
  for (const match of partsSource.matchAll(/export\s+\{\s*[A-Za-z0-9_$]+\s+as\s+([A-Z][A-Za-z0-9_$]*)\s*\}/g)) {
67
- parts.add(match[1]);
59
+ parts.add(match[1])
68
60
  }
69
61
 
70
62
  for (const match of partsSource.matchAll(/export\s+\{\s*([A-Z][A-Za-z0-9_$]*)\s*\}/g)) {
71
- parts.add(match[1]);
63
+ parts.add(match[1])
72
64
  }
73
65
 
74
- return Array.from(parts).sort();
66
+ return Array.from(parts).sort()
75
67
  }
76
68
 
77
69
  function getMacObjectPartKeys(source, macExport) {
78
- const objectStart = source.match(new RegExp(`export const ${macExport} = \\\\{`));
70
+ const objectStart = source.match(new RegExp(`export const ${macExport} = \\\\{`))
79
71
  if (!objectStart) {
80
- return null;
72
+ return null
81
73
  }
82
74
 
83
- const startIndex = objectStart.index + objectStart[0].length;
84
- let depth = 1;
85
- let index = startIndex;
75
+ const startIndex = objectStart.index + objectStart[0].length
76
+ let depth = 1
77
+ let index = startIndex
86
78
 
87
79
  while (index < source.length && depth > 0) {
88
- const char = source[index];
80
+ const char = source[index]
89
81
  if (char === "{") {
90
- depth += 1;
82
+ depth += 1
91
83
  } else if (char === "}") {
92
- depth -= 1;
84
+ depth -= 1
93
85
  }
94
- index += 1;
86
+ index += 1
95
87
  }
96
88
 
97
- const objectBody = source.slice(startIndex, index - 1);
98
- return new Set(
99
- Array.from(objectBody.matchAll(/^\s*([A-Z][A-Za-z0-9_$]*):/gm), (match) => match[1]),
100
- );
89
+ const objectBody = source.slice(startIndex, index - 1)
90
+ return new Set(Array.from(objectBody.matchAll(/^\s*([A-Z][A-Za-z0-9_$]*):/gm), (match) => match[1]))
101
91
  }
102
92
 
103
93
  function fail(message, details = []) {
104
- console.error(message);
94
+ console.error(message)
105
95
  for (const detail of details) {
106
- console.error(`- ${detail}`);
96
+ console.error(`- ${detail}`)
107
97
  }
108
- process.exitCode = 1;
98
+ process.exitCode = 1
109
99
  }
110
100
 
111
- const families = getBaseUiFamilies();
112
- const wrapperSource = readText(wrapperPath);
113
- const gallerySource = readText(galleryPath);
114
- const stylesSource = readText(stylesPath);
115
- const docsSource = readText(docsPath);
116
- const macExports = getMacExports(wrapperSource);
101
+ const families = getBaseUiFamilies()
102
+ const wrapperSource = wrapperPaths.map((filePath) => readText(filePath)).join("\n")
103
+ const gallerySource = listFiles(path.join(repoRoot, "src", "components"))
104
+ .filter((filePath) => /^MacBaseUIGallery.*\.tsx$/.test(filePath))
105
+ .map((filePath) => readText(path.join(repoRoot, "src", "components", filePath)))
106
+ .join("\n")
107
+ const stylesSource = readText(stylesPath)
108
+ const docsSource = readText(docsPath)
109
+ const macExports = getMacExports(wrapperSource)
117
110
 
118
111
  const missingWrappers = families
119
112
  .map((family) => [family, toMacExportName(family)])
120
113
  .filter(([, macExport]) => !macExports.has(macExport))
121
- .map(([family, macExport]) => `${family} -> ${macExport}`);
114
+ .map(([family, macExport]) => `${family} -> ${macExport}`)
122
115
 
123
116
  if (missingWrappers.length > 0) {
124
- fail("Missing macOS wrappers for public @base-ui/react component families.", missingWrappers);
117
+ fail("Missing macOS wrappers for public @base-ui/react component families.", missingWrappers)
125
118
  }
126
119
 
127
- const missingPartWrappers = [];
120
+ const missingPartWrappers = []
128
121
  for (const family of families) {
129
- const macExport = toMacExportName(family);
130
- const baseParts = getBaseUiParts(family);
122
+ const macExport = toMacExportName(family)
123
+ const baseParts = getBaseUiParts(family)
131
124
  if (baseParts.length === 0) {
132
- continue;
125
+ continue
133
126
  }
134
127
 
135
- const macParts = getMacObjectPartKeys(wrapperSource, macExport);
128
+ const macParts = getMacObjectPartKeys(wrapperSource, macExport)
136
129
  if (!macParts) {
137
- continue;
130
+ continue
138
131
  }
139
132
 
140
133
  for (const part of baseParts) {
141
134
  if (!macParts.has(part)) {
142
- missingPartWrappers.push(`${family}.${part} -> ${macExport}.${part}`);
135
+ missingPartWrappers.push(`${family}.${part} -> ${macExport}.${part}`)
143
136
  }
144
137
  }
145
138
  }
146
139
 
147
140
  if (missingPartWrappers.length > 0) {
148
- fail("Missing macOS wrappers for public @base-ui/react namespace parts.", missingPartWrappers);
141
+ fail("Missing macOS wrappers for public @base-ui/react namespace parts.", missingPartWrappers)
149
142
  }
150
143
 
151
144
  const missingDocsRows = families
152
145
  .map((family) => [family, toMacExportName(family)])
153
146
  .filter(([family, macExport]) => {
154
- return !docsSource.includes(`| \`${family}\` | \`${macExport}\` |`);
147
+ const rowPattern = new RegExp(`\\|\\s*\\\`${family}\\\`\\s*\\|\\s*\\\`${macExport}\\\`\\s*\\|`)
148
+ return !rowPattern.test(docsSource)
155
149
  })
156
- .map(([family, macExport]) => `${family} -> ${macExport}`);
150
+ .map(([family, macExport]) => `${family} -> ${macExport}`)
157
151
 
158
152
  if (missingDocsRows.length > 0) {
159
- fail("Missing coverage documentation rows.", missingDocsRows);
153
+ fail("Missing coverage documentation rows.", missingDocsRows)
160
154
  }
161
155
 
162
156
  const requiredVariants = [
@@ -172,11 +166,11 @@ const requiredVariants = [
172
166
  "MacToolbarToggle",
173
167
  "MacSegmentedToggleGroup",
174
168
  "MacSeparatedSegmentedToggleGroup",
175
- ];
169
+ ]
176
170
 
177
- const missingVariants = requiredVariants.filter((variant) => !macExports.has(variant));
171
+ const missingVariants = requiredVariants.filter((variant) => !macExports.has(variant))
178
172
  if (missingVariants.length > 0) {
179
- fail("Missing expected native macOS variant exports.", missingVariants);
173
+ fail("Missing expected native macOS variant exports.", missingVariants)
180
174
  }
181
175
 
182
176
  const requiredNestedVariants = [
@@ -185,63 +179,56 @@ const requiredNestedVariants = [
185
179
  "MacDialog.SheetPopup",
186
180
  "MacDrawer.SidebarPopup",
187
181
  "MacToolbar.IconButton",
188
- ];
182
+ ]
189
183
 
190
184
  const missingNestedVariants = requiredNestedVariants.filter((variantPath) => {
191
- const [exportName, partName] = variantPath.split(".");
192
- return !new RegExp(`export const ${exportName} = [\\s\\S]*?^\\s*${partName}:`, "m").test(
193
- wrapperSource,
194
- );
195
- });
185
+ const [exportName, partName] = variantPath.split(".")
186
+ return !new RegExp(`export const ${exportName} = [\\s\\S]*?^\\s*${partName}:`, "m").test(wrapperSource)
187
+ })
196
188
 
197
189
  if (missingNestedVariants.length > 0) {
198
- fail("Missing expected native macOS nested variant exports.", missingNestedVariants);
190
+ fail("Missing expected native macOS nested variant exports.", missingNestedVariants)
199
191
  }
200
192
 
201
193
  const missingGalleryCoverage = Array.from(macExports)
202
194
  .sort()
203
- .filter((macExport) => !gallerySource.includes(macExport));
195
+ .filter((macExport) => !gallerySource.includes(macExport))
204
196
 
205
197
  if (missingGalleryCoverage.length > 0) {
206
- fail("Missing gallery coverage for public macOS wrapper exports.", missingGalleryCoverage);
198
+ fail("Missing gallery coverage for public macOS wrapper exports.", missingGalleryCoverage)
207
199
  }
208
200
 
209
201
  const wrapperClasses = new Set(
210
202
  Array.from(wrapperSource.matchAll(/"([^"]*hd-mac[^"]*)"/g))
211
203
  .flatMap((match) => match[1].split(/\s+/))
212
204
  .filter(Boolean),
213
- );
205
+ )
214
206
 
215
207
  const missingClassStyles = Array.from(wrapperClasses)
216
208
  .sort()
217
209
  .filter((className) => {
218
- const escapedClassName = className.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
219
- return !new RegExp(`\\.${escapedClassName}(?![a-zA-Z0-9_-])`).test(stylesSource);
220
- });
210
+ const escapedClassName = className.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
211
+ return !new RegExp(`\\.${escapedClassName}(?![a-zA-Z0-9_-])`).test(stylesSource)
212
+ })
221
213
 
222
214
  if (missingClassStyles.length > 0) {
223
- fail("Missing CSS selectors for macOS wrapper classes.", missingClassStyles);
215
+ fail("Missing CSS selectors for macOS wrapper classes.", missingClassStyles)
224
216
  }
225
217
 
226
- const forbiddenPatterns = [
227
- /\bfrom\s+["']baseui(?:\/[^"']*)?["']/,
228
- /\bbaseui\b/,
229
- /\bstyletron\b/i,
230
- /Base Web/,
231
- ];
218
+ const forbiddenPatterns = [/\bfrom\s+["']baseui(?:\/[^"']*)?["']/, /\bbaseui\b/, /\bstyletron\b/i, /Base Web/]
232
219
 
233
220
  function listFiles(directory, relativePrefix = "") {
234
- const files = [];
221
+ const files = []
235
222
  for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
236
- const relativePath = path.join(relativePrefix, entry.name);
237
- const absolutePath = path.join(directory, entry.name);
223
+ const relativePath = path.join(relativePrefix, entry.name)
224
+ const absolutePath = path.join(directory, entry.name)
238
225
  if (entry.isDirectory()) {
239
- files.push(...listFiles(absolutePath, relativePath));
226
+ files.push(...listFiles(absolutePath, relativePath))
240
227
  } else if (entry.isFile()) {
241
- files.push(relativePath);
228
+ files.push(relativePath)
242
229
  }
243
230
  }
244
- return files;
231
+ return files
245
232
  }
246
233
 
247
234
  const sourceFiles = [
@@ -251,27 +238,27 @@ const sourceFiles = [
251
238
  "docs/macos-base-ui-coverage.md",
252
239
  ...listFiles(path.join(repoRoot, "src")).map((filePath) => path.join("src", filePath)),
253
240
  ...listFiles(path.join(repoRoot, "examples")).map((filePath) => path.join("examples", filePath)),
254
- ];
241
+ ]
255
242
 
256
- const forbiddenHits = [];
243
+ const forbiddenHits = []
257
244
  for (const relativeFile of sourceFiles) {
258
- const absoluteFile = path.join(repoRoot, relativeFile);
259
- const text = readText(absoluteFile);
245
+ const absoluteFile = path.join(repoRoot, relativeFile)
246
+ const text = readText(absoluteFile)
260
247
  for (const pattern of forbiddenPatterns) {
261
248
  if (pattern.test(text)) {
262
- forbiddenHits.push(`${relativeFile}: ${pattern}`);
249
+ forbiddenHits.push(`${relativeFile}: ${pattern}`)
263
250
  }
264
251
  }
265
252
  }
266
253
 
267
254
  if (forbiddenHits.length > 0) {
268
- fail("Forbidden Base Web or Styletron references found.", forbiddenHits);
255
+ fail("Forbidden Base Web or Styletron references found.", forbiddenHits)
269
256
  }
270
257
 
271
258
  if (process.exitCode) {
272
- process.exit();
259
+ process.exit()
273
260
  }
274
261
 
275
262
  console.log(
276
263
  `Verified ${families.length} @base-ui/react component families, namespace parts, ${requiredVariants.length} provider/variant exports, ${requiredNestedVariants.length} nested variants, ${wrapperClasses.size} wrapper classes, gallery coverage, and no Base Web references.`,
277
- );
264
+ )