@astryxdesign/core 0.5.0-canary.c47e754 → 0.5.0-canary.ea888ef

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.
@@ -1 +1 @@
1
- {"version":3,"file":"Kbd.d.ts","sourceRoot":"","sources":["../../src/Kbd/Kbd.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AAEH,OAAO,KAA6B,MAAM,OAAO,CAAC;AAIlD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AA0G5C,MAAM,WAAW,QAAS,SAAQ,SAAS,CAAC,eAAe,CAAC;IAC1D,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IACjC;;;;;;;;;;;;OAYG;IACH,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,GAAG,CAAC,EAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,IAAI,EAAC,EAAE,QAAQ,qBAgC3E;yBAhCe,GAAG"}
1
+ {"version":3,"file":"Kbd.d.ts","sourceRoot":"","sources":["../../src/Kbd/Kbd.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AAEH,OAAO,KAA6B,MAAM,OAAO,CAAC;AAGlD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAmI5C,MAAM,WAAW,QAAS,SAAQ,SAAS,CAAC,eAAe,CAAC;IAC1D,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IACjC;;;;;;;;;;;;OAYG;IACH,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,GAAG,CAAC,EAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,IAAI,EAAC,EAAE,QAAQ,qBAgC3E;yBAhCe,GAAG"}
package/dist/Kbd/Kbd.js CHANGED
@@ -15,7 +15,6 @@
15
15
  import React, { useSyncExternalStore } from 'react';
16
16
  import * as stylex from '@stylexjs/stylex';
17
17
  import { mergeProps } from "../utils/index.js";
18
- import { isApplePlatform } from "../utils/isApplePlatform.js";
19
18
  import { themeProps } from "../utils/themeProps.js";
20
19
  import "../theme/tokens.stylex.js";
21
20
  import { colorVars, spacingVars, radiusVars, typographyVars, fontWeightVars, typeScaleVars } from "../theme/tokens.stylex.js";
@@ -99,6 +98,31 @@ function subscribeToPlatformChanges() {
99
98
  function getServerPlatformSnapshot() {
100
99
  return false;
101
100
  }
101
+
102
+ /**
103
+ * Detects whether the current platform is macOS/iOS.
104
+ * Prefers the User-Agent Client Hints API when it names a platform (modern
105
+ * Chrome/Edge), falls back to navigator.platform (deprecated but universally
106
+ * supported) when it is absent or blank.
107
+ */
108
+ function detectMac() {
109
+ if (typeof navigator === 'undefined') {
110
+ return false;
111
+ }
112
+ // Prefer User-Agent Client Hints API (not deprecated)
113
+ const uaData = 'userAgentData' in navigator ? navigator.userAgentData : null;
114
+ if (uaData && typeof uaData === 'object' && 'platform' in uaData) {
115
+ const uaPlatform = uaData.platform;
116
+ // A blank platform is no answer, not a negative one. Builds that rewrite
117
+ // their client-hints identity ship '', so fall through rather than
118
+ // reading it as "not Apple".
119
+ if (typeof uaPlatform === 'string' && uaPlatform.trim() !== '') {
120
+ return /mac/i.test(uaPlatform);
121
+ }
122
+ }
123
+ // Fallback: navigator.platform (deprecated but still shipped everywhere)
124
+ return /Mac|iPhone|iPad|iPod/.test(navigator.platform ?? '');
125
+ }
102
126
  /**
103
127
  * Displays a keyboard shortcut as styled <kbd> elements.
104
128
  *
@@ -122,7 +146,7 @@ export function Kbd({
122
146
  style,
123
147
  ...rest
124
148
  }) {
125
- const isMac = useSyncExternalStore(subscribeToPlatformChanges, isApplePlatform, getServerPlatformSnapshot);
149
+ const isMac = useSyncExternalStore(subscribeToPlatformChanges, detectMac, getServerPlatformSnapshot);
126
150
  const parts = keys.split('+').map(key => key.trim().toLowerCase());
127
151
 
128
152
  // Screen-reader name: the joined spoken labels (e.g. "Command + K"), since
@@ -1 +1 @@
1
- {"version":3,"file":"useHotkeys.d.ts","sourceRoot":"","sources":["../../src/hooks/useHotkeys.ts"],"names":[],"mappings":"AA+BA;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB;;;;;;;;;;;;;;;OAeG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,OAAO,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IACxC;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gEAAgE;IAChE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AA0ED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,CAkClD"}
1
+ {"version":3,"file":"useHotkeys.d.ts","sourceRoot":"","sources":["../../src/hooks/useHotkeys.ts"],"names":[],"mappings":"AA6BA;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB;;;;;;;;;;;;;;;OAeG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,OAAO,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IACxC;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gEAAgE;IAChE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAkGD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,CAkClD"}
@@ -18,15 +18,13 @@
18
18
  * unless a hotkey opts in via `allowInInputs`.
19
19
  *
20
20
  * Platform-aware: `mod` maps to metaKey (⌘) on Apple platforms and
21
- * ctrlKey elsewhere, through the same isApplePlatform util Kbd reads, so
22
- * displayed and handled shortcuts cannot disagree.
21
+ * ctrlKey elsewhere — mirrors the detection used by Kbd.
23
22
  *
24
23
  * SYNC: When modified, update:
25
24
  * - /packages/core/src/hooks/index.ts
26
25
  * - /packages/core/src/hooks/useHotkeys.doc.mjs
27
26
  */
28
27
  import { useEffect, useRef } from 'react';
29
- import { isApplePlatform } from "../utils/isApplePlatform.js";
30
28
 
31
29
  /**
32
30
  * A single keyboard shortcut registration.
@@ -44,6 +42,30 @@ const KEY_ALIASES = {
44
42
  plus: '+'
45
43
  };
46
44
 
45
+ /**
46
+ * Detects whether the current platform is macOS/iOS.
47
+ * Prefers the User-Agent Client Hints API when it names a platform (modern
48
+ * Chrome/Edge), falls back to navigator.platform (deprecated but universally
49
+ * supported) when it is absent or blank.
50
+ * Mirrors the detection used by Kbd so displayed and handled shortcuts agree.
51
+ */
52
+ function isApplePlatform() {
53
+ if (typeof navigator === 'undefined') {
54
+ return false;
55
+ }
56
+ const uaData = 'userAgentData' in navigator ? navigator.userAgentData : null;
57
+ if (uaData && typeof uaData === 'object' && 'platform' in uaData) {
58
+ const uaPlatform = uaData.platform;
59
+ // A blank platform is no answer, not a negative one. Builds that rewrite
60
+ // their client-hints identity ship '', so fall through rather than
61
+ // reading it as "not Apple".
62
+ if (typeof uaPlatform === 'string' && uaPlatform.trim() !== '') {
63
+ return /mac/i.test(uaPlatform);
64
+ }
65
+ }
66
+ return /Mac|iPhone|iPad|iPod/.test(navigator.platform ?? '');
67
+ }
68
+
47
69
  /**
48
70
  * Whether the event target is a typing surface where global shortcuts
49
71
  * should stay silent by default.
@@ -29,7 +29,7 @@ export declare const syntaxTokenDefaults: {
29
29
  readonly '--color-syntax-tag': "var(--color-text-red)";
30
30
  readonly '--color-syntax-attribute': "var(--color-text-teal)";
31
31
  readonly '--color-syntax-property': "var(--color-text-cyan)";
32
- readonly '--color-syntax-punctuation': "var(--color-text-secondary)";
32
+ readonly '--color-syntax-punctuation': "var(--color-text-disabled)";
33
33
  readonly '--color-syntax-background': "var(--color-background-muted)";
34
34
  };
35
35
  export type SyntaxTokenName = keyof typeof syntaxTokenDefaults;
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../../src/theme/syntax/tokens.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;CAgCtB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,mBAAmB,CAAC"}
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../../src/theme/syntax/tokens.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;CA6BtB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,mBAAmB,CAAC"}
@@ -44,11 +44,8 @@ export const syntaxTokenDefaults = {
44
44
  '--color-syntax-attribute': 'var(--color-text-teal)',
45
45
  // property -> cyan text (object properties)
46
46
  '--color-syntax-property': 'var(--color-text-cyan)',
47
- // punctuation -> secondary text (brackets, semicolons). Not disabled text:
48
- // disabled grey is calibrated to fail AA on purpose (WCAG exempts inactive
49
- // controls), and punctuation in a code sample is normal, always-active
50
- // text — see #5386.
51
- '--color-syntax-punctuation': 'var(--color-text-secondary)',
47
+ // punctuation -> disabled text (brackets, semicolons)
48
+ '--color-syntax-punctuation': 'var(--color-text-disabled)',
52
49
  // background -> muted surface
53
50
  '--color-syntax-background': 'var(--color-background-muted)'
54
51
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/core",
3
- "version": "0.5.0-canary.c47e754",
3
+ "version": "0.5.0-canary.ea888ef",
4
4
  "displayName": "Astryx Core",
5
5
  "description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
6
6
  "author": "Meta Open Source",
@@ -661,7 +661,7 @@
661
661
  "react-dom": ">=19.0.0"
662
662
  },
663
663
  "devDependencies": {
664
- "@astryxdesign/cli": "0.5.0-canary.c47e754",
664
+ "@astryxdesign/cli": "0.5.0-canary.ea888ef",
665
665
  "@babel/cli": "^8.0.4",
666
666
  "@babel/core": "^7.29.7",
667
667
  "@babel/preset-react": "^8.0.1",
@@ -23,6 +23,7 @@ describe('Kbd', () => {
23
23
  value: originalPlatform,
24
24
  configurable: true,
25
25
  });
26
+ delete (navigator as {userAgentData?: unknown}).userAgentData;
26
27
  });
27
28
 
28
29
  it('renders a single key', () => {
@@ -54,6 +55,22 @@ describe('Kbd', () => {
54
55
  expect(screen.getByText('\u2318')).toBeInTheDocument(); // \u2318
55
56
  });
56
57
 
58
+ it('reads a blank userAgentData.platform as unknown, not as non-Mac', () => {
59
+ // Builds that rewrite their client-hints identity expose the key with an
60
+ // empty value; navigator.platform is the only surface left that answers.
61
+ Object.defineProperty(navigator, 'userAgentData', {
62
+ value: {platform: ''},
63
+ configurable: true,
64
+ });
65
+ Object.defineProperty(navigator, 'platform', {
66
+ value: 'MacIntel',
67
+ configurable: true,
68
+ });
69
+
70
+ render(<Kbd keys="mod" />);
71
+ expect(screen.getByText('\u2318')).toBeInTheDocument();
72
+ });
73
+
57
74
  it('maps modifier keys to symbols', () => {
58
75
  render(<Kbd keys="ctrl+alt+shift+k" />);
59
76
  expect(screen.getByText('\u2303')).toBeInTheDocument(); // \u2303
package/src/Kbd/Kbd.tsx CHANGED
@@ -16,7 +16,6 @@
16
16
  import React, {useSyncExternalStore} from 'react';
17
17
  import * as stylex from '@stylexjs/stylex';
18
18
  import {mergeProps} from '../utils';
19
- import {isApplePlatform} from '../utils/isApplePlatform';
20
19
  import type {BaseProps} from '../BaseProps';
21
20
  import {themeProps} from '../utils/themeProps';
22
21
  import {
@@ -123,6 +122,31 @@ function getServerPlatformSnapshot(): boolean {
123
122
  return false;
124
123
  }
125
124
 
125
+ /**
126
+ * Detects whether the current platform is macOS/iOS.
127
+ * Prefers the User-Agent Client Hints API when it names a platform (modern
128
+ * Chrome/Edge), falls back to navigator.platform (deprecated but universally
129
+ * supported) when it is absent or blank.
130
+ */
131
+ function detectMac(): boolean {
132
+ if (typeof navigator === 'undefined') {
133
+ return false;
134
+ }
135
+ // Prefer User-Agent Client Hints API (not deprecated)
136
+ const uaData = 'userAgentData' in navigator ? navigator.userAgentData : null;
137
+ if (uaData && typeof uaData === 'object' && 'platform' in uaData) {
138
+ const uaPlatform = (uaData as {platform?: unknown}).platform;
139
+ // A blank platform is no answer, not a negative one. Builds that rewrite
140
+ // their client-hints identity ship '', so fall through rather than
141
+ // reading it as "not Apple".
142
+ if (typeof uaPlatform === 'string' && uaPlatform.trim() !== '') {
143
+ return /mac/i.test(uaPlatform);
144
+ }
145
+ }
146
+ // Fallback: navigator.platform (deprecated but still shipped everywhere)
147
+ return /Mac|iPhone|iPad|iPod/.test(navigator.platform ?? '');
148
+ }
149
+
126
150
  export interface KbdProps extends BaseProps<HTMLSpanElement> {
127
151
  ref?: React.Ref<HTMLSpanElement>;
128
152
  /**
@@ -159,7 +183,7 @@ export interface KbdProps extends BaseProps<HTMLSpanElement> {
159
183
  export function Kbd({keys, ref, xstyle, className, style, ...rest}: KbdProps) {
160
184
  const isMac = useSyncExternalStore(
161
185
  subscribeToPlatformChanges,
162
- isApplePlatform,
186
+ detectMac,
163
187
  getServerPlatformSnapshot,
164
188
  );
165
189
 
@@ -67,6 +67,24 @@ describe('useHotkeys', () => {
67
67
  expect(onPress).toHaveBeenCalledTimes(1);
68
68
  });
69
69
 
70
+ it('reads a blank userAgentData.platform as unknown, not as non-Apple', () => {
71
+ // Builds that rewrite their client-hints identity expose the key with an
72
+ // empty value; navigator.platform is the only surface left that answers.
73
+ vi.stubGlobal('navigator', {
74
+ userAgentData: {platform: ''},
75
+ platform: 'MacIntel',
76
+ });
77
+ const onPress = vi.fn();
78
+ renderHook(() => useHotkeys([{keys: 'mod+k', onPress}]));
79
+
80
+ press('k', {ctrlKey: true});
81
+ expect(onPress).not.toHaveBeenCalled();
82
+
83
+ const event = press('k', {metaKey: true});
84
+ expect(onPress).toHaveBeenCalledTimes(1);
85
+ expect(onPress).toHaveBeenCalledWith(event);
86
+ });
87
+
70
88
  it('does not fire a bare key when modifiers are held', () => {
71
89
  stubApplePlatform();
72
90
  const onPress = vi.fn();
@@ -18,8 +18,7 @@
18
18
  * unless a hotkey opts in via `allowInInputs`.
19
19
  *
20
20
  * Platform-aware: `mod` maps to metaKey (⌘) on Apple platforms and
21
- * ctrlKey elsewhere, through the same isApplePlatform util Kbd reads, so
22
- * displayed and handled shortcuts cannot disagree.
21
+ * ctrlKey elsewhere — mirrors the detection used by Kbd.
23
22
  *
24
23
  * SYNC: When modified, update:
25
24
  * - /packages/core/src/hooks/index.ts
@@ -27,7 +26,6 @@
27
26
  */
28
27
 
29
28
  import {useEffect, useRef} from 'react';
30
- import {isApplePlatform} from '../utils/isApplePlatform';
31
29
 
32
30
  /**
33
31
  * A single keyboard shortcut registration.
@@ -73,6 +71,30 @@ const KEY_ALIASES: Record<string, string> = {
73
71
  plus: '+',
74
72
  };
75
73
 
74
+ /**
75
+ * Detects whether the current platform is macOS/iOS.
76
+ * Prefers the User-Agent Client Hints API when it names a platform (modern
77
+ * Chrome/Edge), falls back to navigator.platform (deprecated but universally
78
+ * supported) when it is absent or blank.
79
+ * Mirrors the detection used by Kbd so displayed and handled shortcuts agree.
80
+ */
81
+ function isApplePlatform(): boolean {
82
+ if (typeof navigator === 'undefined') {
83
+ return false;
84
+ }
85
+ const uaData = 'userAgentData' in navigator ? navigator.userAgentData : null;
86
+ if (uaData && typeof uaData === 'object' && 'platform' in uaData) {
87
+ const uaPlatform = (uaData as {platform?: unknown}).platform;
88
+ // A blank platform is no answer, not a negative one. Builds that rewrite
89
+ // their client-hints identity ship '', so fall through rather than
90
+ // reading it as "not Apple".
91
+ if (typeof uaPlatform === 'string' && uaPlatform.trim() !== '') {
92
+ return /mac/i.test(uaPlatform);
93
+ }
94
+ }
95
+ return /Mac|iPhone|iPad|iPod/.test(navigator.platform ?? '');
96
+ }
97
+
76
98
  /**
77
99
  * Whether the event target is a typing surface where global shortcuts
78
100
  * should stay silent by default.
@@ -44,11 +44,8 @@ export const syntaxTokenDefaults = {
44
44
  '--color-syntax-attribute': 'var(--color-text-teal)',
45
45
  // property -> cyan text (object properties)
46
46
  '--color-syntax-property': 'var(--color-text-cyan)',
47
- // punctuation -> secondary text (brackets, semicolons). Not disabled text:
48
- // disabled grey is calibrated to fail AA on purpose (WCAG exempts inactive
49
- // controls), and punctuation in a code sample is normal, always-active
50
- // text — see #5386.
51
- '--color-syntax-punctuation': 'var(--color-text-secondary)',
47
+ // punctuation -> disabled text (brackets, semicolons)
48
+ '--color-syntax-punctuation': 'var(--color-text-disabled)',
52
49
  // background -> muted surface
53
50
  '--color-syntax-background': 'var(--color-background-muted)',
54
51
  } as const;
@@ -1,18 +0,0 @@
1
- /**
2
- * @file isApplePlatform.ts
3
- * @input Reads navigator.userAgentData.platform and navigator.platform
4
- * @output Exports isApplePlatform
5
- * @position Internal util; the single platform detection behind useHotkeys and Kbd
6
- *
7
- * Deliberately absent from utils/index.ts: packages/core/src/index.ts does
8
- * `export * from './utils'`, so naming it there would publish it as API.
9
- * Import it by path, as interactionModality is imported.
10
- */
11
- /**
12
- * Detects whether the current platform is macOS/iOS.
13
- * Prefers the User-Agent Client Hints API when it names a platform (modern
14
- * Chrome/Edge), falls back to navigator.platform (deprecated but universally
15
- * supported) when it names none.
16
- */
17
- export declare function isApplePlatform(): boolean;
18
- //# sourceMappingURL=isApplePlatform.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"isApplePlatform.d.ts","sourceRoot":"","sources":["../../src/utils/isApplePlatform.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AAEH;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAiBzC"}
@@ -1,37 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /**
4
- * @file isApplePlatform.ts
5
- * @input Reads navigator.userAgentData.platform and navigator.platform
6
- * @output Exports isApplePlatform
7
- * @position Internal util; the single platform detection behind useHotkeys and Kbd
8
- *
9
- * Deliberately absent from utils/index.ts: packages/core/src/index.ts does
10
- * `export * from './utils'`, so naming it there would publish it as API.
11
- * Import it by path, as interactionModality is imported.
12
- */
13
-
14
- /**
15
- * Detects whether the current platform is macOS/iOS.
16
- * Prefers the User-Agent Client Hints API when it names a platform (modern
17
- * Chrome/Edge), falls back to navigator.platform (deprecated but universally
18
- * supported) when it names none.
19
- */
20
- export function isApplePlatform() {
21
- if (typeof navigator === 'undefined') {
22
- return false;
23
- }
24
- const uaData = 'userAgentData' in navigator ? navigator.userAgentData : null;
25
- if (uaData && typeof uaData === 'object' && 'platform' in uaData) {
26
- const uaPlatform = uaData.platform;
27
- const named = typeof uaPlatform === 'string' ? uaPlatform.trim() : '';
28
- // A value that names nothing is no answer, not a negative one: builds that
29
- // rewrite their client-hints identity ship '', and 'Unknown' is the spec's
30
- // own sentinel for "cannot say". Both fall through rather than reading as
31
- // "not Apple".
32
- if (named !== '' && named.toLowerCase() !== 'unknown') {
33
- return /mac/i.test(named);
34
- }
35
- }
36
- return /Mac|iPhone|iPad|iPod/.test(navigator.platform ?? '');
37
- }
@@ -1,61 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /**
4
- * @file isApplePlatform.test.ts
5
- * @input Uses vitest, isApplePlatform
6
- * @output Unit tests for isApplePlatform
7
- * @position Testing; validates isApplePlatform.ts implementation
8
- */
9
-
10
- import {describe, it, expect, vi, afterEach} from 'vitest';
11
- import {isApplePlatform} from './isApplePlatform';
12
-
13
- afterEach(() => {
14
- vi.unstubAllGlobals();
15
- });
16
-
17
- describe('isApplePlatform', () => {
18
- it('trusts a client-hints platform that names one', () => {
19
- vi.stubGlobal('navigator', {
20
- userAgentData: {platform: 'macOS'},
21
- platform: 'Win32',
22
- });
23
- expect(isApplePlatform()).toBe(true);
24
-
25
- vi.stubGlobal('navigator', {
26
- userAgentData: {platform: 'Windows'},
27
- platform: 'MacIntel',
28
- });
29
- expect(isApplePlatform()).toBe(false);
30
- });
31
-
32
- it.each([
33
- ['blank', ''],
34
- ['whitespace', ' '],
35
- ['the spec Unknown sentinel', 'Unknown'],
36
- ['unknown in any case', 'UNKNOWN'],
37
- ['a non-string', null],
38
- ])('falls through to navigator.platform on %s', (_label, platform) => {
39
- vi.stubGlobal('navigator', {
40
- userAgentData: {platform},
41
- platform: 'MacIntel',
42
- });
43
- expect(isApplePlatform()).toBe(true);
44
-
45
- vi.stubGlobal('navigator', {userAgentData: {platform}, platform: 'Win32'});
46
- expect(isApplePlatform()).toBe(false);
47
- });
48
-
49
- it('falls back to navigator.platform when client hints are absent', () => {
50
- vi.stubGlobal('navigator', {platform: 'iPhone'});
51
- expect(isApplePlatform()).toBe(true);
52
-
53
- vi.stubGlobal('navigator', {platform: 'Linux x86_64'});
54
- expect(isApplePlatform()).toBe(false);
55
- });
56
-
57
- it('answers false when there is no navigator at all', () => {
58
- vi.stubGlobal('navigator', undefined);
59
- expect(isApplePlatform()).toBe(false);
60
- });
61
- });
@@ -1,37 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /**
4
- * @file isApplePlatform.ts
5
- * @input Reads navigator.userAgentData.platform and navigator.platform
6
- * @output Exports isApplePlatform
7
- * @position Internal util; the single platform detection behind useHotkeys and Kbd
8
- *
9
- * Deliberately absent from utils/index.ts: packages/core/src/index.ts does
10
- * `export * from './utils'`, so naming it there would publish it as API.
11
- * Import it by path, as interactionModality is imported.
12
- */
13
-
14
- /**
15
- * Detects whether the current platform is macOS/iOS.
16
- * Prefers the User-Agent Client Hints API when it names a platform (modern
17
- * Chrome/Edge), falls back to navigator.platform (deprecated but universally
18
- * supported) when it names none.
19
- */
20
- export function isApplePlatform(): boolean {
21
- if (typeof navigator === 'undefined') {
22
- return false;
23
- }
24
- const uaData = 'userAgentData' in navigator ? navigator.userAgentData : null;
25
- if (uaData && typeof uaData === 'object' && 'platform' in uaData) {
26
- const uaPlatform = (uaData as {platform?: unknown}).platform;
27
- const named = typeof uaPlatform === 'string' ? uaPlatform.trim() : '';
28
- // A value that names nothing is no answer, not a negative one: builds that
29
- // rewrite their client-hints identity ship '', and 'Unknown' is the spec's
30
- // own sentinel for "cannot say". Both fall through rather than reading as
31
- // "not Apple".
32
- if (named !== '' && named.toLowerCase() !== 'unknown') {
33
- return /mac/i.test(named);
34
- }
35
- }
36
- return /Mac|iPhone|iPad|iPod/.test(navigator.platform ?? '');
37
- }