@spaced-out/ui-design-system 0.6.33 → 0.6.35-beta.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.
Files changed (38) hide show
  1. package/.cspell/custom-words.txt +9 -0
  2. package/CHANGELOG.md +14 -0
  3. package/lib/components/Menu/Menu.d.ts +71 -0
  4. package/lib/components/Menu/Menu.d.ts.map +1 -1
  5. package/lib/components/Menu/Menu.js +1028 -185
  6. package/lib/components/Menu/Menu.module.css +148 -27
  7. package/lib/components/Menu/MenuOptionButton.d.ts +23 -2
  8. package/lib/components/Menu/MenuOptionButton.d.ts.map +1 -1
  9. package/lib/components/Menu/MenuOptionButton.js +87 -38
  10. package/lib/components/Menu/index.d.ts +2 -0
  11. package/lib/components/Menu/index.d.ts.map +1 -1
  12. package/lib/components/Menu/index.js +22 -0
  13. package/lib/components/Menu/menuOptionId.d.ts +12 -0
  14. package/lib/components/Menu/menuOptionId.d.ts.map +1 -0
  15. package/lib/components/Menu/menuOptionId.js +18 -0
  16. package/lib/components/Menu/useMenuKeyboardNavigation.d.ts +93 -0
  17. package/lib/components/Menu/useMenuKeyboardNavigation.d.ts.map +1 -0
  18. package/lib/components/Menu/useMenuKeyboardNavigation.js +471 -0
  19. package/lib/components/Menu/useMenuTrigger.d.ts +183 -0
  20. package/lib/components/Menu/useMenuTrigger.d.ts.map +1 -0
  21. package/lib/components/Menu/useMenuTrigger.js +347 -0
  22. package/lib/components/TruncatedTextWithTooltip/TruncatedTextWithTooltip.d.ts.map +1 -1
  23. package/lib/components/TruncatedTextWithTooltip/TruncatedTextWithTooltip.js +66 -53
  24. package/lib/components/TruncatedTextWithTooltip/TruncatedTextWithTooltip.module.css +0 -3
  25. package/lib/hooks/index.d.ts +1 -0
  26. package/lib/hooks/index.d.ts.map +1 -1
  27. package/lib/hooks/index.js +11 -0
  28. package/lib/hooks/useInteractionModality/index.d.ts +2 -0
  29. package/lib/hooks/useInteractionModality/index.d.ts.map +1 -0
  30. package/lib/hooks/useInteractionModality/index.js +16 -0
  31. package/lib/hooks/useInteractionModality/useInteractionModality.d.ts +15 -0
  32. package/lib/hooks/useInteractionModality/useInteractionModality.d.ts.map +1 -0
  33. package/lib/hooks/useInteractionModality/useInteractionModality.js +103 -0
  34. package/lib/utils/click-away/click-away.d.ts +3 -0
  35. package/lib/utils/click-away/click-away.d.ts.map +1 -1
  36. package/lib/utils/click-away/click-away.js +49 -0
  37. package/mcp/package.json +1 -1
  38. package/package.json +1 -1
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useInteractionModality = exports.getInteractionModality = void 0;
7
+ var _react = require("react");
8
+ /*
9
+ * Input modality is a property of the SESSION, not of any one component, so it
10
+ * lives in module scope rather than in state that each consumer would have to
11
+ * thread around. This is the same shape the platform itself uses: `:focus-visible`
12
+ * is implemented by the engine tracking the last interaction globally.
13
+ *
14
+ * Reach for this only when CSS cannot answer the question — `:focus-visible` is
15
+ * the right tool whenever the element you need to style is the one that was
16
+ * interacted with. This exists for the case where it is NOT: a popup deciding
17
+ * whether to show a keyboard cursor has to know how its TRIGGER was operated,
18
+ * and the trigger's own `:focus-visible` cannot be trusted for that, because
19
+ * browsers always match it on text inputs (typing is expected there) — so an
20
+ * input-triggered popup would always look keyboard-opened.
21
+ */
22
+ let currentModality = 'pointer';
23
+ let listening = false;
24
+ const subscribers = new Set();
25
+
26
+ // Keys that navigate even when chorded with a modifier — Cmd+Down is "jump to
27
+ // the end" on macOS and the menus honour it as such.
28
+ const NAVIGATION_KEYS = new Set(['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'PageUp', 'PageDown', 'Tab', 'Enter', ' ']);
29
+ const MODIFIER_KEYS = new Set(['Shift', 'Meta', 'Alt', 'Control']);
30
+ const set = modality => {
31
+ if (currentModality === modality) {
32
+ return;
33
+ }
34
+ currentModality = modality;
35
+ subscribers.forEach(notify => notify(modality));
36
+ };
37
+ const handleKeyDown = event => {
38
+ // A modifier pressed on its own — Shift held for a click, say — is not an
39
+ // interaction, and a chorded shortcut (Cmd+C, Ctrl+S) is a command rather than
40
+ // navigation. Neither should make the session look keyboard-driven. A chorded
41
+ // NAVIGATION key (Cmd+Down) does count: it moves the highlight.
42
+ if (MODIFIER_KEYS.has(event.key)) {
43
+ return;
44
+ }
45
+ if ((event.metaKey || event.altKey || event.ctrlKey) && !NAVIGATION_KEYS.has(event.key)) {
46
+ return;
47
+ }
48
+ set('keyboard');
49
+ };
50
+ const handlePointerDown = () => set('pointer');
51
+
52
+ /*
53
+ * Capture phase, so the modality is recorded before any component handler reacts
54
+ * to the same event.
55
+ *
56
+ * Listening starts when this module is imported, NOT lazily on first read. It has
57
+ * to: the interesting question is usually "how was the thing that just mounted
58
+ * opened?", and the keypress that opened it happened before it existed. Attaching
59
+ * on first read would miss exactly that event and report the default instead.
60
+ */
61
+ const subscribe = notify => {
62
+ // Session-scoped by design: the listeners live for the life of the page, so
63
+ // there is nothing to tear down.
64
+ if (typeof document !== 'undefined' && !listening) {
65
+ listening = true;
66
+ document.addEventListener('keydown', handleKeyDown, true);
67
+ document.addEventListener('pointerdown', handlePointerDown, true);
68
+ }
69
+ if (!notify) {
70
+ return () => undefined;
71
+ }
72
+ subscribers.add(notify);
73
+ return () => {
74
+ subscribers.delete(notify);
75
+ };
76
+ };
77
+
78
+ // Start listening at import time — see the note on `subscribe`.
79
+ subscribe();
80
+
81
+ /**
82
+ * How the user last interacted with the page, read imperatively.
83
+ *
84
+ * Use this when the answer is needed during render or in an effect rather than
85
+ * as reactive state — for example a popup deciding on mount whether it was opened
86
+ * by keyboard, where re-rendering on later modality changes would be wrong.
87
+ */
88
+ const getInteractionModality = () => {
89
+ subscribe();
90
+ return currentModality;
91
+ };
92
+
93
+ /**
94
+ * How the user last interacted with the page, as reactive state. Re-renders the
95
+ * caller when the user switches between keyboard and pointer.
96
+ */
97
+ exports.getInteractionModality = getInteractionModality;
98
+ const useInteractionModality = () => {
99
+ const [modality, setModality] = (0, _react.useState)(() => getInteractionModality());
100
+ (0, _react.useEffect)(() => subscribe(setModality), []);
101
+ return modality;
102
+ };
103
+ exports.useInteractionModality = useInteractionModality;
@@ -56,11 +56,14 @@ export declare class ClickAway<TTrigger extends HTMLElement = HTMLButtonElement,
56
56
  el: HTMLElement | null | undefined;
57
57
  boundaryRef: BoundaryRefType<TBoundary>;
58
58
  triggerRef: TriggerRefType<TTrigger>;
59
+ previouslyFocused: HTMLElement | null;
60
+ closedByOutsideClick: boolean;
59
61
  componentDidMount(): void;
60
62
  componentDidUpdate(_prevProps: Readonly<ClickAwayProps<TTrigger, TBoundary>>, prevState: Readonly<ClickAwayState>): void;
61
63
  componentWillUnmount(): void;
62
64
  render(): React.ReactNode;
63
65
  handleOpenClick: () => void;
66
+ restoreFocus: () => void;
64
67
  handleCloseClick: (evt: MouseEvent) => void;
65
68
  handleCloseOnEscapeKeypress: (evt?: KeyboardEvent) => void;
66
69
  forceClose: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"click-away.d.ts","sourceRoot":"","sources":["../../../src/utils/click-away/click-away.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"click-away.d.ts","sourceRoot":"","sources":["../../../src/utils/click-away/click-away.ts"],"names":[],"mappings":"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"}
@@ -24,6 +24,17 @@ class ClickAway extends React.Component {
24
24
  el = null;
25
25
  boundaryRef = /*#__PURE__*/React.createRef();
26
26
  triggerRef = /*#__PURE__*/React.createRef();
27
+ // Element that had focus right before the popup opened. Restored on close so
28
+ // keyboard focus returns to the trigger (Escape, a selection, Tab past the
29
+ // popup's edge) — the job FloatingFocusManager's returnFocus does for the
30
+ // dropdown family until they move onto this.
31
+ previouslyFocused = null;
32
+ // Set by handleCloseClick for the close that follows. A click on the page is
33
+ // the user moving on: focus belongs wherever they clicked (or on <body>), and
34
+ // pulling it back onto the trigger would re-run the trigger's own focus
35
+ // handling — a Typeahead opens its menu on focus, so it would reopen the very
36
+ // popup the click dismissed.
37
+ closedByOutsideClick = false;
27
38
  componentDidMount() {
28
39
  if (this.el) {
29
40
  this.setState({
@@ -49,6 +60,12 @@ class ClickAway extends React.Component {
49
60
  capture: true
50
61
  });
51
62
  window.document.removeEventListener('keyup', this.handleCloseOnEscapeKeypress);
63
+ if (this.closedByOutsideClick) {
64
+ this.previouslyFocused = null;
65
+ } else {
66
+ this.restoreFocus();
67
+ }
68
+ this.closedByOutsideClick = false;
52
69
  }
53
70
  }
54
71
  }
@@ -92,11 +109,42 @@ class ClickAway extends React.Component {
92
109
  if (this.el) {
93
110
  pageBottom = this.pageBottom();
94
111
  }
112
+
113
+ // Capture the focused element before opening (still the trigger at this
114
+ // point, before the popup mounts and moves focus into itself) so it can be
115
+ // restored on close.
116
+ if (!this.state.isOpen && typeof window.document !== 'undefined') {
117
+ this.previouslyFocused = window.document.activeElement;
118
+ }
95
119
  this.setState({
96
120
  isOpen: !this.state.isOpen,
97
121
  pageBottom
98
122
  }, this.handleOnChange);
99
123
  };
124
+
125
+ // Return focus to the trigger when the popup closes — but only if focus was
126
+ // lost to the document body (the focused popup element was unmounted). If the
127
+ // user moved focus elsewhere (e.g. clicked another control), leave it there.
128
+ restoreFocus = () => {
129
+ const previous = this.previouslyFocused;
130
+ this.previouslyFocused = null;
131
+ if (typeof window.document === 'undefined') {
132
+ return;
133
+ }
134
+ const active = window.document.activeElement;
135
+ const focusLost = !active || active === window.document.body;
136
+ if (!focusLost) {
137
+ return;
138
+ }
139
+ // `previouslyFocused` is <body> whenever the opening click did not land on a
140
+ // natively focusable element — notably Safari and Firefox on macOS, which do
141
+ // not focus a <button> on click. Restoring it would be a no-op that strands
142
+ // focus at the top of the document, so fall back to the trigger itself.
143
+ const target = previous && previous !== window.document.body ? previous : this.triggerRef.current;
144
+ if (target && window.document.contains(target)) {
145
+ target.focus();
146
+ }
147
+ };
100
148
  handleCloseClick = evt => {
101
149
  if (evt.target instanceof Node && this.props.containsNestedFloatingPortals && this.boundaryRef.current?.parentElement && (this.boundaryRef.current.parentElement === evt.target || this.boundaryRef.current.parentElement.contains(evt.target))) {
102
150
  return;
@@ -107,6 +155,7 @@ class ClickAway extends React.Component {
107
155
  if (evt.target instanceof Node && this.triggerRef && (this.triggerRef.current === evt.target || this.triggerRef.current?.contains(evt.target))) {
108
156
  return;
109
157
  }
158
+ this.closedByOutsideClick = true;
110
159
  this.setState({
111
160
  isOpen: false
112
161
  }, this.handleOnChange);
package/mcp/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spaced-out/genesis-mcp",
3
- "version": "1.0.45",
3
+ "version": "1.0.47",
4
4
  "description": "MCP server for Genesis UI Design System - provides AI assistants with access to components, hooks, and design tokens",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spaced-out/ui-design-system",
3
- "version": "0.6.33",
3
+ "version": "0.6.35-beta.0",
4
4
  "description": "Sense UI components library",
5
5
  "author": {
6
6
  "name": "Spaced Out"