reshaped 2.11.9 → 2.11.11

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.
@@ -0,0 +1,18 @@
1
+ import type * as T from "../Flyout.types";
2
+ /**
3
+ * Calculate styles for the current position
4
+ */
5
+ declare const calculatePosition: (args: T.Options & {
6
+ triggerBounds: DOMRect;
7
+ flyoutBounds: DOMRect;
8
+ scopeOffset: Record<"left" | "top", number>;
9
+ }) => {
10
+ styles: {
11
+ left: number;
12
+ top: number;
13
+ width: number;
14
+ height: number;
15
+ };
16
+ position: T.Position;
17
+ };
18
+ export default calculatePosition;
@@ -0,0 +1,97 @@
1
+ const SCREEN_OFFSET = 16;
2
+ const getRTLPosition = (position) => {
3
+ if (position.includes("start"))
4
+ return position.replace("start", "end");
5
+ if (position.includes("end"))
6
+ return position.replace("end", "start");
7
+ return position;
8
+ };
9
+ /**
10
+ * Get a position value which centers 2 elements vertically or horizontally
11
+ */
12
+ const centerBySize = (originSize, targetSize) => {
13
+ return Math.floor(originSize / 2 - targetSize / 2);
14
+ };
15
+ /**
16
+ * Calculate styles for the current position
17
+ */
18
+ const calculatePosition = (args) => {
19
+ const { triggerBounds, flyoutBounds, scopeOffset, position: passedPosition, rtl, width } = args;
20
+ let left = 0;
21
+ let top = 0;
22
+ let position = passedPosition;
23
+ if (rtl)
24
+ position = getRTLPosition(position);
25
+ if (width === "full" || width === "trigger") {
26
+ position = position.includes("top") ? "top" : "bottom";
27
+ }
28
+ switch (position) {
29
+ case "bottom":
30
+ case "top":
31
+ left = centerBySize(triggerBounds.width, flyoutBounds.width) + triggerBounds.left;
32
+ break;
33
+ case "start":
34
+ case "start-top":
35
+ case "start-bottom":
36
+ left = triggerBounds.left - triggerBounds.width;
37
+ break;
38
+ case "end":
39
+ case "end-top":
40
+ case "end-bottom":
41
+ left = triggerBounds.right;
42
+ break;
43
+ case "top-start":
44
+ case "bottom-start":
45
+ left = triggerBounds.left;
46
+ break;
47
+ case "top-end":
48
+ case "bottom-end":
49
+ left = triggerBounds.right - flyoutBounds.width;
50
+ break;
51
+ default:
52
+ break;
53
+ }
54
+ switch (position) {
55
+ case "top":
56
+ case "top-start":
57
+ case "top-end":
58
+ top = triggerBounds.top - flyoutBounds.height;
59
+ break;
60
+ case "bottom":
61
+ case "bottom-start":
62
+ case "bottom-end":
63
+ top = triggerBounds.bottom;
64
+ break;
65
+ case "start":
66
+ case "end":
67
+ top = centerBySize(triggerBounds.height, flyoutBounds.height) + triggerBounds.top;
68
+ break;
69
+ case "start-top":
70
+ case "end-top":
71
+ top = triggerBounds.top;
72
+ break;
73
+ case "start-bottom":
74
+ case "end-bottom":
75
+ top = triggerBounds.bottom - triggerBounds.height;
76
+ break;
77
+ default:
78
+ break;
79
+ }
80
+ if (top === undefined || left === undefined) {
81
+ throw Error(`[Reshaped, flyout]: ${position} position is not valid`);
82
+ }
83
+ top = Math.round(top + (window.scrollY || 0) - scopeOffset.top);
84
+ left = Math.round(left + (window.scrollX || 0) - scopeOffset.left);
85
+ let widthStyle = Math.ceil(flyoutBounds.width);
86
+ const height = Math.ceil(flyoutBounds.height);
87
+ if (width === "full") {
88
+ left = SCREEN_OFFSET;
89
+ widthStyle = window.innerWidth - SCREEN_OFFSET * 2;
90
+ }
91
+ else if (width === "trigger") {
92
+ widthStyle = triggerBounds.width;
93
+ }
94
+ const styles = { left, top, width: widthStyle, height };
95
+ return { styles, position };
96
+ };
97
+ export default calculatePosition;
@@ -3,7 +3,7 @@ import React from "react";
3
3
  * Types
4
4
  */
5
5
  type Callback = (e: KeyboardEvent) => void;
6
- type PressedKeys = Record<string, KeyboardEvent>;
6
+ type PressedMap = Record<string, KeyboardEvent>;
7
7
  type Hotkeys = Record<string, Callback | null>;
8
8
  type HotkeyOptions = {
9
9
  preventDefault?: boolean;
@@ -12,20 +12,17 @@ type Context = {
12
12
  isPressed: (key: string) => boolean;
13
13
  addHotkeys: (hotkeys: Hotkeys, ref: React.RefObject<HTMLElement | null>, options?: HotkeyOptions) => (() => void) | undefined;
14
14
  };
15
+ type HotkeyData = {
16
+ callback: Callback;
17
+ ref: React.RefObject<HTMLElement | null>;
18
+ options: HotkeyOptions;
19
+ };
15
20
  export declare class HotkeyStore {
16
- hotkeyMap: Record<string, {
17
- data: Set<{
18
- callback: Callback;
19
- ref: React.RefObject<HTMLElement | null>;
20
- options: HotkeyOptions;
21
- }>;
22
- used: boolean;
23
- }>;
21
+ hotkeyMap: Record<string, Set<HotkeyData>>;
24
22
  getSize: () => number;
25
23
  bindHotkeys: (hotkeys: Hotkeys, ref: React.RefObject<HTMLElement | null>, options: HotkeyOptions) => void;
26
24
  unbindHotkeys: (hotkeys: Hotkeys) => void;
27
- handleKeyDown: (pressedMap: PressedKeys, e: KeyboardEvent) => void;
28
- handleKeyUp: (e: KeyboardEvent) => void;
25
+ handleKeyDown: (pressedMap: PressedMap, e: KeyboardEvent) => void;
29
26
  }
30
27
  /**
31
28
  * Components / Hooks
@@ -24,29 +24,6 @@ const getEventKey = (e) => {
24
24
  }
25
25
  return e.key.toLowerCase();
26
26
  };
27
- const getCombinations = (pressedKeys) => {
28
- const result = [];
29
- const hotkey = pressedKeys.join(COMBINATION_DELIMETER);
30
- const id = getHotkeyId(hotkey);
31
- const sortedKeys = id.split(COMBINATION_DELIMETER);
32
- const f = (prefix, items) => {
33
- items.forEach((item, i) => {
34
- const nextId = prefix ? `${prefix}+${item}` : item;
35
- result.push(nextId);
36
- f(nextId, items.slice(i + 1));
37
- });
38
- };
39
- f("", sortedKeys);
40
- return result;
41
- };
42
- const walkPressedCombinations = (pressed, cb) => {
43
- const pressedKeys = Object.keys(pressed);
44
- if (!pressedKeys.length)
45
- return;
46
- getCombinations(pressedKeys).forEach((pressedId) => {
47
- cb(pressedId);
48
- });
49
- };
50
27
  const walkHotkeys = (hotkeys, cb) => {
51
28
  Object.keys(hotkeys).forEach((key) => {
52
29
  key.split(",").forEach((hotkey) => {
@@ -66,9 +43,9 @@ export class HotkeyStore {
66
43
  if (!hotkeyData)
67
44
  return;
68
45
  if (!this.hotkeyMap[id]) {
69
- this.hotkeyMap[id] = { data: new Set(), used: false };
46
+ this.hotkeyMap[id] = new Set();
70
47
  }
71
- this.hotkeyMap[id].data.add({ callback: hotkeyData, ref, options });
48
+ this.hotkeyMap[id].add({ callback: hotkeyData, ref, options });
72
49
  });
73
50
  };
74
51
  this.unbindHotkeys = (hotkeys) => {
@@ -76,23 +53,34 @@ export class HotkeyStore {
76
53
  var _a, _b;
77
54
  if (!hotkeyCallback)
78
55
  return;
79
- (_a = this.hotkeyMap[id]) === null || _a === void 0 ? void 0 : _a.data.forEach((data) => {
56
+ (_a = this.hotkeyMap[id]) === null || _a === void 0 ? void 0 : _a.forEach((data) => {
80
57
  if (data.callback === hotkeyCallback) {
81
- this.hotkeyMap[id].data.delete(data);
58
+ this.hotkeyMap[id].delete(data);
82
59
  }
83
60
  });
84
- if (!((_b = this.hotkeyMap[id]) === null || _b === void 0 ? void 0 : _b.data.size)) {
61
+ if (!((_b = this.hotkeyMap[id]) === null || _b === void 0 ? void 0 : _b.size)) {
85
62
  delete this.hotkeyMap[id];
86
63
  }
87
64
  });
88
65
  };
89
66
  this.handleKeyDown = (pressedMap, e) => {
90
- walkPressedCombinations(pressedMap, (pressedId) => {
91
- const hotkeyData = this.hotkeyMap[pressedId];
92
- if (!hotkeyData || hotkeyData.used)
67
+ const pressedKeys = Object.keys(pressedMap);
68
+ if (!pressedKeys.length)
69
+ return;
70
+ const pressedId = getHotkeyId(pressedKeys.join(COMBINATION_DELIMETER));
71
+ const pressedFormattedKeys = pressedId.split(COMBINATION_DELIMETER);
72
+ const hotkeyData = this.hotkeyMap[pressedId];
73
+ /**
74
+ * Support for `mod` that represents both Mac and Win keyboards
75
+ */
76
+ const hotkeyControlModData = pressedFormattedKeys.includes("control") &&
77
+ this.hotkeyMap[pressedId.replace("control", "mod")];
78
+ const hotkeyMetaModData = pressedFormattedKeys.includes("meta") && this.hotkeyMap[pressedId.replace("meta", "mod")];
79
+ [hotkeyData, hotkeyControlModData, hotkeyMetaModData].forEach((hotkeyData) => {
80
+ if (!hotkeyData)
93
81
  return;
94
- if (hotkeyData === null || hotkeyData === void 0 ? void 0 : hotkeyData.data.size) {
95
- hotkeyData.data.forEach((data) => {
82
+ if (hotkeyData === null || hotkeyData === void 0 ? void 0 : hotkeyData.size) {
83
+ hotkeyData.forEach((data) => {
96
84
  var _a;
97
85
  if (((_a = data.ref) === null || _a === void 0 ? void 0 : _a.current) &&
98
86
  !(e.target === data.ref.current || data.ref.current.contains(e.target))) {
@@ -103,24 +91,10 @@ export class HotkeyStore {
103
91
  resolvedEvent === null || resolvedEvent === void 0 ? void 0 : resolvedEvent.preventDefault();
104
92
  }
105
93
  data.callback(resolvedEvent);
106
- /**
107
- * While meta is pressed - other keys keyup won't trigger and
108
- * we want to allow calling the same shortcut multiple times while meta was pressed
109
- */
110
- if (!(resolvedEvent === null || resolvedEvent === void 0 ? void 0 : resolvedEvent.metaKey))
111
- this.hotkeyMap[pressedId].used = true;
112
94
  });
113
95
  }
114
96
  });
115
97
  };
116
- this.handleKeyUp = (e) => {
117
- const id = getHotkeyId(e.key);
118
- walkHotkeys(this.hotkeyMap, (hotkeyId, data) => {
119
- const hotkeyIds = hotkeyId.split(COMBINATION_DELIMETER);
120
- if (hotkeyIds.includes(id))
121
- data.used = false;
122
- });
123
- };
124
98
  }
125
99
  }
126
100
  const globalHotkeyStore = new HotkeyStore();
@@ -141,18 +115,13 @@ export const SingletonHotkeysProvider = (props) => {
141
115
  if (!eventKey)
142
116
  return;
143
117
  pressedMap[eventKey] = e;
144
- if (eventKey === "meta" || eventKey === "control") {
145
- pressedMap.mod = e;
146
- }
147
118
  setTriggerCount(Object.keys(pressedMap).length);
148
119
  // Key up won't trigger for other keys while Meta is pressed so we need to cache them
149
120
  // and remove on Meta keyup
150
- if (eventKey === "meta" || e.metaKey) {
121
+ if (e.metaKey)
151
122
  modifiedKeys.push(...Object.keys(pressedMap));
152
- }
153
- if (pressedMap.Meta) {
123
+ if (pressedMap.Meta)
154
124
  modifiedKeys.push(eventKey);
155
- }
156
125
  }, [hooksCount]);
157
126
  const removePressedKey = React.useCallback((e) => {
158
127
  if (hooksCount === 0)
@@ -168,7 +137,6 @@ export const SingletonHotkeysProvider = (props) => {
168
137
  modifiedKeys.forEach((key) => {
169
138
  if (!pressedMap[key])
170
139
  return;
171
- globalHotkeyStore.handleKeyUp(pressedMap[key]);
172
140
  delete pressedMap[key];
173
141
  });
174
142
  modifiedKeys = [];
@@ -200,7 +168,6 @@ export const SingletonHotkeysProvider = (props) => {
200
168
  if (!e.key)
201
169
  return;
202
170
  removePressedKey(e);
203
- globalHotkeyStore.handleKeyUp(e);
204
171
  }, [removePressedKey]);
205
172
  React.useEffect(() => {
206
173
  window.addEventListener("keydown", handleWindowKeyDown);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "reshaped",
3
3
  "description": "Professionally crafted design system in React & Figma for building products of any scale and complexity",
4
- "version": "2.11.9",
4
+ "version": "2.11.11",
5
5
  "license": "MIT",
6
6
  "email": "hello@reshaped.so",
7
7
  "homepage": "https://reshaped.so",
@@ -7,6 +7,7 @@ type ReleaseOptions = {
7
7
  type TrapOptions = {
8
8
  onNavigateOutside?: () => void;
9
9
  includeTrigger?: boolean;
10
+ initialFocusEl?: FocusableElement;
10
11
  mode?: TrapMode;
11
12
  };
12
13
  declare class TrapFocus {
@@ -60,7 +60,7 @@ class TrapFocus {
60
60
  * and create a chain item
61
61
  */
62
62
  this.trap = (options = {}) => {
63
- const { mode = "dialog", includeTrigger } = options;
63
+ const { mode = "dialog", includeTrigger, initialFocusEl } = options;
64
64
  const trigger = getActiveElement();
65
65
  const focusable = getFocusableElements(this.root, {
66
66
  additionalElement: includeTrigger ? trigger : undefined,
@@ -84,14 +84,14 @@ class TrapFocus {
84
84
  if (mode === "dialog")
85
85
  this.screenReaderTrap.trap();
86
86
  this.mutationObserver.observe(this.root, { childList: true, subtree: true });
87
- if (!focusable.length)
87
+ if (!focusable.length && !initialFocusEl)
88
88
  return;
89
89
  this.addListeners();
90
90
  // Don't add back to the chain if we're traversing back
91
91
  const tailItem = TrapFocus.chain.tailId && TrapFocus.chain.get(TrapFocus.chain.tailId);
92
92
  if (!tailItem || this.root !== tailItem.data.root) {
93
93
  this.chainId = TrapFocus.chain.add(this);
94
- focusElement(focusable[0], { pseudoFocus });
94
+ focusElement(initialFocusEl || focusable[0], { pseudoFocus });
95
95
  }
96
96
  this.trapped = true;
97
97
  };
@@ -1,27 +0,0 @@
1
- import React from "react";
2
- /**
3
- * Typings
4
- */
5
- export type FlyoutPosition = "bottom" | "bottom-start" | "bottom-end" | "top" | "top-start" | "top-end" | "start" | "start-top" | "start-bottom" | "end" | "end-top" | "end-bottom";
6
- export type FlyoutWidth = "trigger" | string;
7
- type ElementRef = React.RefObject<HTMLElement>;
8
- type PassedFlyoutOptions = {
9
- width?: FlyoutWidth;
10
- position?: FlyoutPosition;
11
- defaultActive?: boolean;
12
- forcePosition?: boolean;
13
- };
14
- type FlyoutStyles = React.CSSProperties;
15
- type FlyoutState = {
16
- styles: FlyoutStyles;
17
- position?: FlyoutPosition;
18
- status: "idle" | "rendered" | "positioned" | "visible" | "hidden";
19
- };
20
- type UseFlyout = (originRef: ElementRef, targetRef: ElementRef, options: PassedFlyoutOptions) => Pick<FlyoutState, "styles" | "position" | "status"> & {
21
- updatePosition: () => void;
22
- render: () => void;
23
- hide: () => void;
24
- remove: () => void;
25
- };
26
- declare const useFlyout: UseFlyout;
27
- export default useFlyout;