@dxos/plugin-navtree 0.8.1-staging.391c573 → 0.8.1-staging.97aedb1

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 (62) hide show
  1. package/dist/lib/browser/{app-graph-builder-BL7LRXCM.mjs → app-graph-builder-26D3MUCA.mjs} +2 -2
  2. package/dist/lib/browser/{chunk-AOAGODQB.mjs → chunk-4RQJXI4D.mjs} +2 -2
  3. package/dist/lib/browser/{chunk-GXNOX2XZ.mjs → chunk-DN43QXSX.mjs} +147 -187
  4. package/dist/lib/browser/chunk-DN43QXSX.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-KOXMEAOS.mjs → chunk-IEW3JD5C.mjs} +3 -2
  6. package/dist/lib/browser/{chunk-KOXMEAOS.mjs.map → chunk-IEW3JD5C.mjs.map} +3 -3
  7. package/dist/lib/browser/index.mjs +3 -5
  8. package/dist/lib/browser/index.mjs.map +2 -2
  9. package/dist/lib/browser/{intent-resolver-U25VBFH3.mjs → intent-resolver-BZMUS24C.mjs} +3 -3
  10. package/dist/lib/browser/{keyboard-MUBRTPI5.mjs → keyboard-IQXHKUWM.mjs} +2 -2
  11. package/dist/lib/browser/meta.json +1 -1
  12. package/dist/lib/browser/{react-surface-SG4QBA2U.mjs → react-surface-3HCM3SHJ.mjs} +5 -11
  13. package/dist/lib/browser/react-surface-3HCM3SHJ.mjs.map +7 -0
  14. package/dist/lib/browser/{state-IEFQTUPG.mjs → state-M5UJ3MHL.mjs} +3 -3
  15. package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
  16. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  17. package/dist/types/src/components/L0Menu.d.ts +2 -1
  18. package/dist/types/src/components/L0Menu.d.ts.map +1 -1
  19. package/dist/types/src/components/L1Panels.d.ts.map +1 -1
  20. package/dist/types/src/components/NavTree.d.ts.map +1 -1
  21. package/dist/types/src/components/NavTreeContainer.d.ts.map +1 -1
  22. package/dist/types/src/components/UserAccountAvatar.d.ts +11 -0
  23. package/dist/types/src/components/UserAccountAvatar.d.ts.map +1 -0
  24. package/dist/types/src/components/{HaloButton.stories.d.ts → UserAccountAvatar.stories.d.ts} +3 -3
  25. package/dist/types/src/components/UserAccountAvatar.stories.d.ts.map +1 -0
  26. package/dist/types/src/components/index.d.ts +0 -1
  27. package/dist/types/src/components/index.d.ts.map +1 -1
  28. package/dist/types/src/meta.d.ts +2 -3
  29. package/dist/types/src/meta.d.ts.map +1 -1
  30. package/dist/types/src/testing/generator.d.ts +3 -3
  31. package/dist/types/src/testing/generator.d.ts.map +1 -1
  32. package/dist/types/src/util.d.ts +0 -8
  33. package/dist/types/src/util.d.ts.map +1 -1
  34. package/package.json +31 -30
  35. package/src/NavTreePlugin.tsx +0 -1
  36. package/src/capabilities/react-surface.tsx +1 -12
  37. package/src/components/L0Menu.tsx +46 -32
  38. package/src/components/L1Panels.tsx +15 -19
  39. package/src/components/NavTree.tsx +12 -2
  40. package/src/components/NavTreeContainer.tsx +11 -5
  41. package/src/components/NavTreeItemColumns.tsx +3 -3
  42. package/src/components/UserAccountAvatar.stories.tsx +21 -0
  43. package/src/components/UserAccountAvatar.tsx +35 -0
  44. package/src/components/index.ts +0 -1
  45. package/src/meta.ts +3 -2
  46. package/src/testing/generator.ts +3 -3
  47. package/src/util.ts +1 -42
  48. package/dist/lib/browser/chunk-GXNOX2XZ.mjs.map +0 -7
  49. package/dist/lib/browser/react-surface-SG4QBA2U.mjs.map +0 -7
  50. package/dist/types/src/components/HaloButton.d.ts +0 -12
  51. package/dist/types/src/components/HaloButton.d.ts.map +0 -1
  52. package/dist/types/src/components/HaloButton.stories.d.ts.map +0 -1
  53. package/dist/types/src/components/NotchStart.d.ts +0 -3
  54. package/dist/types/src/components/NotchStart.d.ts.map +0 -1
  55. package/src/components/HaloButton.stories.tsx +0 -21
  56. package/src/components/HaloButton.tsx +0 -41
  57. package/src/components/NotchStart.tsx +0 -30
  58. /package/dist/lib/browser/{app-graph-builder-BL7LRXCM.mjs.map → app-graph-builder-26D3MUCA.mjs.map} +0 -0
  59. /package/dist/lib/browser/{chunk-AOAGODQB.mjs.map → chunk-4RQJXI4D.mjs.map} +0 -0
  60. /package/dist/lib/browser/{intent-resolver-U25VBFH3.mjs.map → intent-resolver-BZMUS24C.mjs.map} +0 -0
  61. /package/dist/lib/browser/{keyboard-MUBRTPI5.mjs.map → keyboard-IQXHKUWM.mjs.map} +0 -0
  62. /package/dist/lib/browser/{state-IEFQTUPG.mjs.map → state-M5UJ3MHL.mjs.map} +0 -0
@@ -30,13 +30,13 @@ import {
30
30
  import type { StackItemRearrangeHandler } from '@dxos/react-ui-stack';
31
31
  import { Tabs } from '@dxos/react-ui-tabs';
32
32
  import { mx } from '@dxos/react-ui-theme';
33
- import { arrayMove } from '@dxos/util';
33
+ import { arrayMove, getFirstTwoRenderableChars } from '@dxos/util';
34
34
 
35
35
  import { useNavTreeContext } from './NavTreeContext';
36
- import { NotchStart } from './NotchStart';
36
+ import { UserAccountAvatar } from './UserAccountAvatar';
37
37
  import { useLoadDescendents } from '../hooks';
38
38
  import { NAVTREE_PLUGIN } from '../meta';
39
- import { getFirstTwoRenderableChars, l0ItemType } from '../util';
39
+ import { l0ItemType } from '../util';
40
40
 
41
41
  type L0ItemData = { id: L0ItemProps['item']['id']; type: 'l0Item' };
42
42
 
@@ -45,6 +45,7 @@ type L0ItemProps = {
45
45
  parent?: Node<any>;
46
46
  path: string[];
47
47
  pinned?: boolean;
48
+ userAccount?: boolean;
48
49
  onRearrange?: StackItemRearrangeHandler<L0ItemData>;
49
50
  };
50
51
 
@@ -95,7 +96,7 @@ const l0Breakpoints: Record<string, string> = {
95
96
  lg: 'hidden lg:grid',
96
97
  };
97
98
 
98
- const L0Item = ({ item, parent, path, pinned, onRearrange }: L0ItemProps) => {
99
+ const L0Item = ({ item, parent, path, pinned, userAccount, onRearrange }: L0ItemProps) => {
99
100
  const { t } = useTranslation(NAVTREE_PLUGIN);
100
101
  const itemElement = useRef<HTMLElement | null>(null);
101
102
  const [closestEdge, setEdge] = useState<Edge | null>(null);
@@ -184,31 +185,40 @@ const L0Item = ({ item, parent, path, pinned, onRearrange }: L0ItemProps) => {
184
185
  )}
185
186
  ref={itemElement}
186
187
  >
187
- <div
188
- role='none'
189
- data-frame={true}
190
- className={mx(
191
- 'absolute grid dx-focus-ring-group-indicator transition-colors',
192
- type === 'tab' || pinned ? 'rounded' : 'rounded-full',
193
- pinned
194
- ? 'bg-transparent group-hover/l0i:bg-groupSurface inset-inline-3 inset-block-0.5'
195
- : 'bg-groupSurface inset-inline-3 inset-block-2',
196
- )}
197
- {...(hue && { style: { background: `var(--dx-${hue}Surface)` } })}
198
- >
199
- {(item.properties.icon && (
200
- <Icon icon={item.properties.icon} size={pinned ? 5 : 7} classNames='place-self-center' {...hueFgStyle} />
201
- )) ||
202
- (type === 'tab' && item.properties.disposition !== 'pin-end' ? (
203
- <span role='img' className='place-self-center text-3xl font-light' {...hueFgStyle}>
204
- {avatarValue}
205
- </span>
206
- ) : (
207
- item.properties.icon && (
208
- <Icon icon='ph--planet--regular' size={pinned ? 5 : 7} classNames='place-self-center' {...hueFgStyle} />
209
- )
210
- ))}
211
- </div>
188
+ {userAccount ? (
189
+ <UserAccountAvatar
190
+ userId={item.properties.userId}
191
+ hue={item.properties.hue}
192
+ emoji={item.properties.emoji}
193
+ status={item.properties.status}
194
+ />
195
+ ) : (
196
+ <div
197
+ role='none'
198
+ data-frame={true}
199
+ className={mx(
200
+ 'absolute grid dx-focus-ring-group-indicator transition-colors',
201
+ type === 'tab' || pinned ? 'rounded' : 'rounded-full',
202
+ pinned
203
+ ? 'bg-transparent group-hover/l0i:bg-groupSurface inset-inline-3 inset-block-0.5'
204
+ : 'bg-groupSurface inset-inline-3 inset-block-2',
205
+ )}
206
+ {...(hue && { style: { background: `var(--dx-${hue}Surface)` } })}
207
+ >
208
+ {(item.properties.icon && (
209
+ <Icon icon={item.properties.icon} size={pinned ? 5 : 7} classNames='place-self-center' {...hueFgStyle} />
210
+ )) ||
211
+ (type === 'tab' && item.properties.disposition !== 'pin-end' ? (
212
+ <span role='img' className='place-self-center text-3xl font-light' {...hueFgStyle}>
213
+ {avatarValue}
214
+ </span>
215
+ ) : (
216
+ item.properties.icon && (
217
+ <Icon icon='ph--planet--regular' size={pinned ? 5 : 7} classNames='place-self-center' {...hueFgStyle} />
218
+ )
219
+ ))}
220
+ </div>
221
+ )}
212
222
  <div
213
223
  role='none'
214
224
  className='hidden group-aria-selected/l0i:block absolute inline-start-0 inset-block-4 is-1 bg-accentSurface rounded-ie'
@@ -280,11 +290,13 @@ const L0Collection = ({ item, path, parent }: L0ItemProps) => {
280
290
  export const L0Menu = ({
281
291
  topLevelItems,
282
292
  pinnedItems,
293
+ userAccountItem,
283
294
  parent,
284
295
  path,
285
296
  }: {
286
297
  topLevelItems: Node<any>[];
287
298
  pinnedItems: Node<any>[];
299
+ userAccountItem: Node<any>;
288
300
  parent?: Node<any>;
289
301
  path: string[];
290
302
  }) => {
@@ -316,9 +328,11 @@ export const L0Menu = ({
316
328
  <L0Item key={item.id} item={item} parent={parent} path={path} pinned />
317
329
  ))}
318
330
  </div>
319
- <div role='none' className='grid p-2 app-no-drag'>
320
- <NotchStart />
321
- </div>
331
+ {userAccountItem && (
332
+ <div role='none' className='grid py-2 app-no-drag'>
333
+ <L0Item key={userAccountItem.id} item={userAccountItem} parent={parent} path={path} userAccount />
334
+ </div>
335
+ )}
322
336
  <div
323
337
  role='none'
324
338
  className='hidden [body[data-platform="darwin"]_&]:block absolute block-start-0 is-[calc(var(--l0-size)-1px)] bs-[calc(40px+0.25rem)]'
@@ -6,7 +6,7 @@ import React, { useMemo } from 'react';
6
6
 
7
7
  import { useLayout } from '@dxos/app-framework';
8
8
  import { type Node } from '@dxos/app-graph';
9
- import { IconButton, toLocalizedString, useTranslation } from '@dxos/react-ui';
9
+ import { Button, Icon, toLocalizedString, useTranslation } from '@dxos/react-ui';
10
10
  import { Tree } from '@dxos/react-ui-list';
11
11
  import { Tabs } from '@dxos/react-ui-tabs';
12
12
  import { hoverableControlItem, hoverableOpenControlItem, mx } from '@dxos/react-ui-theme';
@@ -36,24 +36,20 @@ const L1Panel = ({ item, path, currentItemId, onBack }: L1PanelProps) => {
36
36
  >
37
37
  {item.id === currentItemId && (
38
38
  <>
39
- <h2 className='flex items-center border-be border-separator pis-4 app-drag'>
40
- {item.properties.disposition === 'pin-end' && (
41
- <IconButton
42
- label={t('back label')}
43
- iconOnly
44
- icon='ph--caret-left--regular'
45
- size={5}
46
- density='fine'
47
- variant='ghost'
48
- classNames={mx(
49
- 'shrink-0 mie-2 !p-0',
50
- 'pointer-fine:pli-1',
51
- hoverableControlItem,
52
- hoverableOpenControlItem,
53
- )}
54
- onClick={onBack}
55
- />
56
- )}
39
+ <h2 className='flex items-center border-be border-separator app-drag'>
40
+ <Button
41
+ variant='ghost'
42
+ density='fine'
43
+ classNames={mx(
44
+ 'is-6 pli-0 dx-focus-ring-inset',
45
+ !item.id.startsWith('!') && 'invisible',
46
+ hoverableControlItem,
47
+ hoverableOpenControlItem,
48
+ )}
49
+ onClick={onBack}
50
+ >
51
+ <Icon icon='ph--caret-left--regular' size={3} />
52
+ </Button>
57
53
  <span className='flex-1 truncate cursor-default'>{toLocalizedString(item.properties.label, t)}</span>
58
54
  <NavTreeItemColumns path={path} item={item} open />
59
55
  </h2>
@@ -26,7 +26,11 @@ export const NavTree = ({ id, root }: NavTreeProps) => {
26
26
  [topLevelActions, topLevelCollections, topLevelWorkspaces, topLevelNavigation],
27
27
  );
28
28
  const pinnedItems = getItems(root, 'pin-end').toSorted((a, b) => byPosition(a.properties, b.properties));
29
- const topLevelItems = useMemo(() => [...l0Items, ...pinnedItems], [l0Items, pinnedItems]);
29
+ const userAccountItem = getItems(root, 'user-account')[0];
30
+ const topLevelItems = useMemo(
31
+ () => [...l0Items, ...pinnedItems, userAccountItem],
32
+ [l0Items, pinnedItems, userAccountItem],
33
+ );
30
34
 
31
35
  useLoadDescendents(root);
32
36
  const path = useMemo(() => [id], [id]);
@@ -34,7 +38,13 @@ export const NavTree = ({ id, root }: NavTreeProps) => {
34
38
  return (
35
39
  // NOTE(thure): 74px (rather than rem) is intentional in order to match the size of macOS windowing controls
36
40
  <Tabs.Root value={tab} orientation='vertical' verticalVariant='stateless' classNames='relative'>
37
- <L0Menu topLevelItems={l0Items} pinnedItems={pinnedItems} path={path} parent={root} />
41
+ <L0Menu
42
+ topLevelItems={l0Items}
43
+ pinnedItems={pinnedItems}
44
+ userAccountItem={userAccountItem}
45
+ path={path}
46
+ parent={root}
47
+ />
38
48
  <L1Panels topLevelItems={topLevelItems} path={path} currentItemId={tab} onBack={onBack} />
39
49
  </Tabs.Root>
40
50
  );
@@ -17,6 +17,7 @@ import {
17
17
  useLayout,
18
18
  } from '@dxos/app-framework';
19
19
  import { isAction, isActionLike, type Node } from '@dxos/app-graph';
20
+ import { PLANK_COMPANION_TYPE } from '@dxos/plugin-deck/types';
20
21
  import { isEchoObject, isSpace } from '@dxos/react-client/echo';
21
22
  import { useMediaQuery } from '@dxos/react-ui';
22
23
  import { isTreeData, type TreeData, type PropsFromTreeItem } from '@dxos/react-ui-list';
@@ -45,6 +46,9 @@ const renderItemEnd = ({ node, open }: { node: Node; open: boolean }) => (
45
46
  <Surface role='navtree-item-end' data={{ id: node.id, subject: node.data, open }} limit={1} />
46
47
  );
47
48
 
49
+ const getChildrenFilter = (node: Node): node is Node =>
50
+ untracked(() => !isActionLike(node) && node.type !== PLANK_COMPANION_TYPE);
51
+
48
52
  export type NavTreeContainerProps = {
49
53
  popoverAnchorId?: string;
50
54
  topbar?: boolean;
@@ -64,12 +68,14 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
64
68
  return graph.nodes(node ?? graph.root, {
65
69
  filter: (node): node is Node => {
66
70
  return untracked(() => {
67
- const action = isAction(node);
68
- if (!disposition) {
71
+ if (node.properties.disposition === 'hidden') {
72
+ return false;
73
+ } else if (!disposition) {
74
+ const action = isAction(node);
69
75
  return !action || node.properties.disposition === 'item';
76
+ } else {
77
+ return node.properties.disposition === disposition;
70
78
  }
71
-
72
- return node.properties.disposition === disposition;
73
79
  });
74
80
  },
75
81
  });
@@ -79,7 +85,7 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
79
85
 
80
86
  const getProps = useCallback(
81
87
  (node: Node, path: string[]): PropsFromTreeItem => {
82
- const children = getChildren(graph, node, undefined, path);
88
+ const children = getChildren(graph, node, getChildrenFilter, path);
83
89
  const parentOf =
84
90
  children.length > 0 ? children.map(({ id }) => id) : node.properties.role === 'branch' ? [] : undefined;
85
91
  return {
@@ -22,10 +22,10 @@ export const NavTreeItemColumns = memo(({ path, item, open }: NavTreeItemColumns
22
22
 
23
23
  const { actions: _actions, groupedActions } = getActions(item);
24
24
  const [primaryAction, ...secondaryActions] = _actions.toSorted((a, b) =>
25
- a.properties?.disposition === 'toolbar' ? -1 : 1,
25
+ a.properties?.disposition === 'list-item-primary' ? -1 : 1,
26
26
  );
27
27
 
28
- const actions = (primaryAction?.properties?.disposition === 'toolbar' ? secondaryActions : _actions)
28
+ const actions = (primaryAction?.properties?.disposition === 'list-item-primary' ? secondaryActions : _actions)
29
29
  .flatMap((action) => (isAction(action) ? [action] : []))
30
30
  .filter((action) => !action.properties?.hidden);
31
31
 
@@ -36,7 +36,7 @@ export const NavTreeItemColumns = memo(({ path, item, open }: NavTreeItemColumns
36
36
 
37
37
  return (
38
38
  <div role='none' className='contents app-no-drag'>
39
- {primaryAction?.properties?.disposition === 'toolbar' && !primaryAction?.properties?.disabled ? (
39
+ {primaryAction?.properties?.disposition === 'list-item-primary' && !primaryAction?.properties?.disabled ? (
40
40
  <NavTreeItemAction
41
41
  testId={primaryAction.properties?.testId}
42
42
  label={toLocalizedString(primaryAction.properties?.label, t)}
@@ -0,0 +1,21 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import '@dxos-theme';
6
+
7
+ import { type Meta } from '@storybook/react';
8
+ import React from 'react';
9
+
10
+ import { UserAccountAvatar } from './UserAccountAvatar';
11
+
12
+ export const Default = (props: any) => {
13
+ return <UserAccountAvatar {...props} />;
14
+ };
15
+
16
+ const meta: Meta<typeof UserAccountAvatar> = {
17
+ title: 'plugins/plugin-navtree/UserAccountAvatar',
18
+ component: UserAccountAvatar,
19
+ };
20
+
21
+ export default meta;
@@ -0,0 +1,35 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { Avatar, type AvatarStatus, type Size } from '@dxos/react-ui';
8
+ import { hexToFallback } from '@dxos/util';
9
+
10
+ export type UserAccountAvatarProps = {
11
+ size?: Size;
12
+ userId?: string;
13
+ hue?: string;
14
+ emoji?: string;
15
+ status?: AvatarStatus;
16
+ };
17
+
18
+ export const UserAccountAvatar = (props: UserAccountAvatarProps) => {
19
+ const fallbackValue = hexToFallback(props.userId ?? '0');
20
+
21
+ return (
22
+ <div className='grid place-items-center' data-joyride='welcome/account'>
23
+ <Avatar.Root>
24
+ <Avatar.Content
25
+ variant='circle'
26
+ size={props.size ?? 12}
27
+ status={props.status ?? 'active'}
28
+ hue={props.hue || fallbackValue.hue}
29
+ fallback={props.emoji || fallbackValue.emoji}
30
+ data-testid='treeview.userAccount'
31
+ />
32
+ </Avatar.Root>
33
+ </div>
34
+ );
35
+ };
@@ -8,5 +8,4 @@ export * from './NavTree';
8
8
  export * from './NavTreeContainer';
9
9
  export * from './NavTreeContext';
10
10
  export * from './NavTreeDocumentTitle';
11
- export * from './NotchStart';
12
11
  export * from './types';
package/src/meta.ts CHANGED
@@ -11,6 +11,7 @@ export const KEY_BINDING = 'KeyBinding';
11
11
  // TODO(wittjosiah): Factor out.
12
12
  export const COMMANDS_DIALOG = `${NAVTREE_PLUGIN}/CommandsDialog`;
13
13
 
14
- export const meta = {
14
+ export const meta: PluginMeta = {
15
15
  id: NAVTREE_PLUGIN,
16
- } satisfies PluginMeta;
16
+ name: 'Navtree',
17
+ };
@@ -15,12 +15,12 @@ import { range } from '@dxos/util';
15
15
  export type TestItem = { id: string; type: string } & Record<string, any>;
16
16
 
17
17
  type ObjectDataGenerator = {
18
- createSchema?: () => S.Schema<any>;
18
+ createSchema?: () => S.Schema.AnyNoContext;
19
19
  createData: () => any;
20
20
  };
21
21
 
22
22
  type ObjectFactory<T extends ReactiveObject<any>> = {
23
- schema?: S.Schema<any>; // TODO(burdon): Support both typed and expando schema.
23
+ schema?: S.Schema.AnyNoContext; // TODO(burdon): Support both typed and expando schema.
24
24
  createObject: () => T;
25
25
  };
26
26
 
@@ -87,7 +87,7 @@ export class TestObjectGenerator {
87
87
  }, {});
88
88
  }
89
89
 
90
- get schema(): S.Schema<any>[] {
90
+ get schema(): S.Schema.AnyNoContext[] {
91
91
  return Object.values(this.factories).map((f) => f.schema!);
92
92
  }
93
93
 
package/src/util.ts CHANGED
@@ -75,7 +75,7 @@ export const sortActions = (actions: Action[]): Action[] =>
75
75
  return 0;
76
76
  }
77
77
 
78
- if (a.properties.disposition === 'toolbar') {
78
+ if (a.properties.disposition === 'list-item-primary') {
79
79
  return -1;
80
80
  }
81
81
 
@@ -139,44 +139,3 @@ export const l0ItemType = (item: Node<any>) => {
139
139
  return 'tab';
140
140
  }
141
141
  };
142
-
143
- // Regular expression to match renderable characters
144
- // Excludes control characters, combining marks, and other non-renderable characters
145
- const renderableCharRegex =
146
- /^(?![\p{Control}\p{Mark}\p{Separator}\p{Surrogate}\p{Unassigned}])[\p{L}\p{N}\p{P}\p{S}\p{Emoji}]$/u;
147
-
148
- /**
149
- * Returns the first two renderable characters from a string that are separated by non-word characters.
150
- * Handles Unicode characters correctly.
151
- *
152
- * @param {string} label - The input string to process
153
- * @returns {[string, string]} Array containing the two characters, or empty strings if not found
154
- */
155
- export const getFirstTwoRenderableChars = (label: string) => {
156
- // Convert string to array of Unicode characters
157
- const characters = Array.from(label);
158
-
159
- // Keep track of found renderable characters
160
- const result = ['', ''];
161
- let foundFirst = false;
162
-
163
- for (let i = 0; i < characters.length; i++) {
164
- const char = characters[i];
165
-
166
- if (renderableCharRegex.test(char)) {
167
- if (!foundFirst) {
168
- result[0] = char;
169
- foundFirst = true;
170
- } else {
171
- // Check if there's at least one non-word character between the first and current char
172
- const textBetween = characters.slice(result[0].length, i).join('');
173
- if (/[^\p{L}\p{N}_]/u.test(textBetween)) {
174
- result[1] = char;
175
- break;
176
- }
177
- }
178
- }
179
- }
180
-
181
- return result;
182
- };