@expofp/ui 0.0.0-experimental.2d6c681

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 (192) hide show
  1. package/README.md +30 -0
  2. package/dist/components/Alert/Alert.d.ts +20 -0
  3. package/dist/components/Alert/Alert.js +23 -0
  4. package/dist/components/Alert/index.d.ts +3 -0
  5. package/dist/components/Alert/index.js +1 -0
  6. package/dist/components/Autocomplete/Autocomplete.d.ts +28 -0
  7. package/dist/components/Autocomplete/Autocomplete.js +292 -0
  8. package/dist/components/Autocomplete/index.d.ts +3 -0
  9. package/dist/components/Autocomplete/index.js +1 -0
  10. package/dist/components/Badge/Badge.d.ts +14 -0
  11. package/dist/components/Badge/Badge.js +15 -0
  12. package/dist/components/Badge/index.d.ts +3 -0
  13. package/dist/components/Badge/index.js +1 -0
  14. package/dist/components/Button/Button.d.ts +30 -0
  15. package/dist/components/Button/Button.js +41 -0
  16. package/dist/components/Button/index.d.ts +3 -0
  17. package/dist/components/Button/index.js +1 -0
  18. package/dist/components/Checkbox/Checkbox.d.ts +19 -0
  19. package/dist/components/Checkbox/Checkbox.js +19 -0
  20. package/dist/components/Checkbox/index.d.ts +3 -0
  21. package/dist/components/Checkbox/index.js +1 -0
  22. package/dist/components/CheckboxButton/CheckboxButton.d.ts +12 -0
  23. package/dist/components/CheckboxButton/CheckboxButton.js +19 -0
  24. package/dist/components/CheckboxButton/index.d.ts +3 -0
  25. package/dist/components/CheckboxButton/index.js +1 -0
  26. package/dist/components/DownloadList/DownloadList.d.ts +15 -0
  27. package/dist/components/DownloadList/DownloadList.js +9 -0
  28. package/dist/components/DownloadList/index.d.ts +3 -0
  29. package/dist/components/DownloadList/index.js +2 -0
  30. package/dist/components/Draggable/Draggable.d.ts +9 -0
  31. package/dist/components/Draggable/Draggable.js +92 -0
  32. package/dist/components/Draggable/index.d.ts +3 -0
  33. package/dist/components/Draggable/index.js +2 -0
  34. package/dist/components/EntityItem/EntityItem.d.ts +46 -0
  35. package/dist/components/EntityItem/EntityItem.js +29 -0
  36. package/dist/components/EntityItem/index.d.ts +3 -0
  37. package/dist/components/EntityItem/index.js +1 -0
  38. package/dist/components/EventBadge/EventBadge.d.ts +13 -0
  39. package/dist/components/EventBadge/EventBadge.js +10 -0
  40. package/dist/components/EventBadge/index.d.ts +3 -0
  41. package/dist/components/EventBadge/index.js +1 -0
  42. package/dist/components/Gallery/Gallery.d.ts +24 -0
  43. package/dist/components/Gallery/Gallery.js +28 -0
  44. package/dist/components/Gallery/GalleryBadges/GalleryBadges.d.ts +8 -0
  45. package/dist/components/Gallery/GalleryBadges/GalleryBadges.js +5 -0
  46. package/dist/components/Gallery/GalleryControls/GalleryControls.d.ts +11 -0
  47. package/dist/components/Gallery/GalleryControls/GalleryControls.js +5 -0
  48. package/dist/components/Gallery/GalleryImg/GalleryImg.d.ts +13 -0
  49. package/dist/components/Gallery/GalleryImg/GalleryImg.js +37 -0
  50. package/dist/components/Gallery/GalleryItem/GalleryItem.d.ts +14 -0
  51. package/dist/components/Gallery/GalleryItem/GalleryItem.js +6 -0
  52. package/dist/components/Gallery/GalleryModal/GalleryModal.d.ts +20 -0
  53. package/dist/components/Gallery/GalleryModal/GalleryModal.js +90 -0
  54. package/dist/components/Gallery/GalleryPreLoader.d.ts +7 -0
  55. package/dist/components/Gallery/GalleryPreLoader.js +29 -0
  56. package/dist/components/Gallery/TransformImg/TransformImg.d.ts +10 -0
  57. package/dist/components/Gallery/TransformImg/TransformImg.js +15 -0
  58. package/dist/components/Gallery/index.d.ts +4 -0
  59. package/dist/components/Gallery/index.js +1 -0
  60. package/dist/components/Gallery/types.d.ts +6 -0
  61. package/dist/components/Gallery/types.js +1 -0
  62. package/dist/components/HeatmapLegend/HeatmapLegend.d.ts +15 -0
  63. package/dist/components/HeatmapLegend/HeatmapLegend.js +11 -0
  64. package/dist/components/HeatmapLegend/index.d.ts +3 -0
  65. package/dist/components/HeatmapLegend/index.js +2 -0
  66. package/dist/components/HighlightText/HighlightText.d.ts +7 -0
  67. package/dist/components/HighlightText/HighlightText.js +14 -0
  68. package/dist/components/HighlightText/index.d.ts +2 -0
  69. package/dist/components/HighlightText/index.js +1 -0
  70. package/dist/components/LevelBadge/LevelBadge.d.ts +9 -0
  71. package/dist/components/LevelBadge/LevelBadge.js +10 -0
  72. package/dist/components/LevelBadge/index.d.ts +3 -0
  73. package/dist/components/LevelBadge/index.js +2 -0
  74. package/dist/components/LevelSelector/LevelSelector.d.ts +18 -0
  75. package/dist/components/LevelSelector/LevelSelector.js +91 -0
  76. package/dist/components/LevelSelector/index.d.ts +3 -0
  77. package/dist/components/LevelSelector/index.js +2 -0
  78. package/dist/components/MapControls/MapControls.d.ts +33 -0
  79. package/dist/components/MapControls/MapControls.js +38 -0
  80. package/dist/components/MapControls/index.d.ts +3 -0
  81. package/dist/components/MapControls/index.js +2 -0
  82. package/dist/components/MobileToggleButton/MobileToggleButton.d.ts +11 -0
  83. package/dist/components/MobileToggleButton/MobileToggleButton.js +8 -0
  84. package/dist/components/MobileToggleButton/index.d.ts +3 -0
  85. package/dist/components/MobileToggleButton/index.js +1 -0
  86. package/dist/components/Modal/Modal.d.ts +34 -0
  87. package/dist/components/Modal/Modal.js +103 -0
  88. package/dist/components/Modal/index.d.ts +3 -0
  89. package/dist/components/Modal/index.js +1 -0
  90. package/dist/components/MultiSelectGroups/MultiSelectGroups.d.ts +37 -0
  91. package/dist/components/MultiSelectGroups/MultiSelectGroups.js +80 -0
  92. package/dist/components/MultiSelectGroups/index.d.ts +3 -0
  93. package/dist/components/MultiSelectGroups/index.js +1 -0
  94. package/dist/components/Overlay/Overlay.d.ts +30 -0
  95. package/dist/components/Overlay/Overlay.js +250 -0
  96. package/dist/components/Overlay/OverlayHeaderContext.d.ts +7 -0
  97. package/dist/components/Overlay/OverlayHeaderContext.js +7 -0
  98. package/dist/components/Overlay/OverlayKeyboardInsetContext.d.ts +7 -0
  99. package/dist/components/Overlay/OverlayKeyboardInsetContext.js +7 -0
  100. package/dist/components/Overlay/OverlayScrollContext.d.ts +7 -0
  101. package/dist/components/Overlay/OverlayScrollContext.js +7 -0
  102. package/dist/components/Overlay/index.d.ts +5 -0
  103. package/dist/components/Overlay/index.js +3 -0
  104. package/dist/components/OverlayGrip/OverlayGrip.d.ts +11 -0
  105. package/dist/components/OverlayGrip/OverlayGrip.js +8 -0
  106. package/dist/components/OverlayGrip/index.d.ts +3 -0
  107. package/dist/components/OverlayGrip/index.js +2 -0
  108. package/dist/components/PermissionNotice/PermissionNotice.d.ts +16 -0
  109. package/dist/components/PermissionNotice/PermissionNotice.js +11 -0
  110. package/dist/components/PermissionNotice/index.d.ts +3 -0
  111. package/dist/components/PermissionNotice/index.js +2 -0
  112. package/dist/components/PlanSelector/PlanSelector.d.ts +13 -0
  113. package/dist/components/PlanSelector/PlanSelector.js +10 -0
  114. package/dist/components/PlanSelector/index.d.ts +3 -0
  115. package/dist/components/PlanSelector/index.js +2 -0
  116. package/dist/components/Radio/Radio.d.ts +15 -0
  117. package/dist/components/Radio/Radio.js +13 -0
  118. package/dist/components/Radio/index.d.ts +3 -0
  119. package/dist/components/Radio/index.js +1 -0
  120. package/dist/components/RebookingNotes/RebookingNotes.d.ts +11 -0
  121. package/dist/components/RebookingNotes/RebookingNotes.js +18 -0
  122. package/dist/components/RebookingNotes/index.d.ts +3 -0
  123. package/dist/components/RebookingNotes/index.js +1 -0
  124. package/dist/components/RebookingRadioGroup/RebookingRadioGroup.d.ts +17 -0
  125. package/dist/components/RebookingRadioGroup/RebookingRadioGroup.js +13 -0
  126. package/dist/components/RebookingRadioGroup/index.d.ts +3 -0
  127. package/dist/components/RebookingRadioGroup/index.js +1 -0
  128. package/dist/components/ScrollableRow/ScrollableRow.d.ts +21 -0
  129. package/dist/components/ScrollableRow/ScrollableRow.js +155 -0
  130. package/dist/components/ScrollableRow/index.d.ts +3 -0
  131. package/dist/components/ScrollableRow/index.js +2 -0
  132. package/dist/components/Share/Share.d.ts +22 -0
  133. package/dist/components/Share/Share.js +24 -0
  134. package/dist/components/Share/index.d.ts +3 -0
  135. package/dist/components/Share/index.js +1 -0
  136. package/dist/components/ShowMoreButton/ShowMoreButton.d.ts +11 -0
  137. package/dist/components/ShowMoreButton/ShowMoreButton.js +7 -0
  138. package/dist/components/ShowMoreButton/index.d.ts +3 -0
  139. package/dist/components/ShowMoreButton/index.js +1 -0
  140. package/dist/components/SidebarActions/SidebarActions.d.ts +26 -0
  141. package/dist/components/SidebarActions/SidebarActions.js +32 -0
  142. package/dist/components/SidebarActions/index.d.ts +3 -0
  143. package/dist/components/SidebarActions/index.js +2 -0
  144. package/dist/components/SimpleRow/SimpleRow.d.ts +15 -0
  145. package/dist/components/SimpleRow/SimpleRow.js +11 -0
  146. package/dist/components/SimpleRow/index.d.ts +3 -0
  147. package/dist/components/SimpleRow/index.js +2 -0
  148. package/dist/components/Spinner/Spinner.d.ts +9 -0
  149. package/dist/components/Spinner/Spinner.js +8 -0
  150. package/dist/components/Spinner/index.d.ts +3 -0
  151. package/dist/components/Spinner/index.js +1 -0
  152. package/dist/components/ToggleButton/ToggleButton.d.ts +17 -0
  153. package/dist/components/ToggleButton/ToggleButton.js +8 -0
  154. package/dist/components/ToggleButton/index.d.ts +3 -0
  155. package/dist/components/ToggleButton/index.js +1 -0
  156. package/dist/components/ToggleSwitch/ToggleSwitch.d.ts +12 -0
  157. package/dist/components/ToggleSwitch/ToggleSwitch.js +10 -0
  158. package/dist/components/ToggleSwitch/index.d.ts +3 -0
  159. package/dist/components/ToggleSwitch/index.js +1 -0
  160. package/dist/components/TouchHand/TouchHand.d.ts +5 -0
  161. package/dist/components/TouchHand/TouchHand.js +7 -0
  162. package/dist/components/TouchHand/index.d.ts +2 -0
  163. package/dist/components/TouchHand/index.js +1 -0
  164. package/dist/components/VenuePicker/VenuePicker.d.ts +23 -0
  165. package/dist/components/VenuePicker/VenuePicker.js +70 -0
  166. package/dist/components/VenuePicker/index.d.ts +3 -0
  167. package/dist/components/VenuePicker/index.js +2 -0
  168. package/dist/components/WayfindingFloorSelector/WayfindingFloorSelector.d.ts +24 -0
  169. package/dist/components/WayfindingFloorSelector/WayfindingFloorSelector.js +27 -0
  170. package/dist/components/WayfindingFloorSelector/index.d.ts +3 -0
  171. package/dist/components/WayfindingFloorSelector/index.js +1 -0
  172. package/dist/index.d.ts +74 -0
  173. package/dist/index.js +77 -0
  174. package/dist/shared/activeElement.d.ts +7 -0
  175. package/dist/shared/activeElement.js +14 -0
  176. package/dist/shared/debounce.d.ts +2 -0
  177. package/dist/shared/debounce.js +9 -0
  178. package/dist/shared/hooks/useKeyboardInset.d.ts +9 -0
  179. package/dist/shared/hooks/useKeyboardInset.js +84 -0
  180. package/dist/shared/hooks/useOnClickOutside.d.ts +14 -0
  181. package/dist/shared/hooks/useOnClickOutside.js +43 -0
  182. package/dist/shared/hooks/useOnEscape.d.ts +14 -0
  183. package/dist/shared/hooks/useOnEscape.js +31 -0
  184. package/dist/shared/hooks/useSheetDrag.d.ts +28 -0
  185. package/dist/shared/hooks/useSheetDrag.js +274 -0
  186. package/dist/shared/hooks/useWindowSize.d.ts +5 -0
  187. package/dist/shared/hooks/useWindowSize.js +38 -0
  188. package/dist/shared/interactive.d.ts +2 -0
  189. package/dist/shared/interactive.js +4 -0
  190. package/dist/shared/selector-option.d.ts +7 -0
  191. package/dist/shared/selector-option.js +1 -0
  192. package/package.json +73 -0
package/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # @expofp/ui
2
+
3
+ Shared **pure, presentational** React components for the ExpoFP SDK. Bundled with the packages that
4
+ depend on it — no separate install.
5
+
6
+ **Scope rule:** props-in, markup-out components only — no domain logic, no MobX stores, no data
7
+ fetching, no side effects. Nothing here may know about floor plans, booths, exhibitors, or events, or
8
+ import from a domain package. Components receive everything they render via props; stateful wiring
9
+ (Containers) lives in the consuming package. The test: _could it ship in a general-purpose React
10
+ component library unchanged?_ If not, it belongs next to the domain that owns it.
11
+
12
+ ## Styling
13
+
14
+ Components import their own `.scss`, which expects the SDK's shared SCSS layer (design tokens,
15
+ mixins, icon glyphs) via the `@styles` alias. The published package ships **JS only** — no compiled
16
+ CSS and no `.scss` sources. It is therefore consumed **from source** by `@expofp/floorplan`'s bundler
17
+ (the `@expofp/source` export condition), which compiles the SCSS into its own output. `@expofp/ui` is
18
+ an internal building block and is not intended to be styled standalone from its published `dist`.
19
+
20
+ `src/styles` (the primitives `_vars`, `_utils`, `main`, `_icons`) and `public/fonts` are the canonical
21
+ home of this layer — `@expofp/floorplan` points its own `@styles` alias here instead of keeping a
22
+ copy. If a third consumer ever needs it, split it into a dedicated `@expofp/styles` package; until
23
+ then, one extra alias hop is simpler than a package with a single dependent.
24
+
25
+ - Build: `nx build ui`
26
+ - Test (unit): `nx test ui`
27
+ - Test (stories, browser): `nx test-storybook ui`
28
+ - Storybook (dev): `nx storybook ui`
29
+ - Storybook (build): `nx build-storybook ui`
30
+ - Lint: `nx lint ui`
@@ -0,0 +1,20 @@
1
+ import './Alert.scss';
2
+ import React, { type ReactNode } from 'react';
3
+ export type AlertVariant = 'info' | 'warning' | 'error' | 'success' | 'blank';
4
+ export type AlertSize = 'sm' | 'md';
5
+ export type AlertPosition = 'top-right' | 'bottom-right';
6
+ export interface AlertProps {
7
+ children?: ReactNode;
8
+ position?: AlertPosition;
9
+ variant?: AlertVariant;
10
+ size?: AlertSize;
11
+ inline?: boolean;
12
+ showIcon?: boolean;
13
+ closable?: boolean;
14
+ title?: string;
15
+ closeLabel: string;
16
+ onClose?: () => void;
17
+ }
18
+ declare const Alert: ({ children, position, variant, inline, showIcon, size, closable, title, closeLabel, onClose, }: AlertProps) => React.JSX.Element;
19
+ export default Alert;
20
+ //# sourceMappingURL=Alert.d.ts.map
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // @ts-nocheck
3
+ import './Alert.scss';
4
+ import cn from 'classnames';
5
+ const Alert = ({ children, position, variant = 'info', inline = false, showIcon = true, size = 'md', closable = false, title, closeLabel, onClose, }) => {
6
+ const alertIcon = () => {
7
+ switch (variant) {
8
+ case 'error':
9
+ return 'icon-close-solid';
10
+ case 'success':
11
+ return 'icon-checkmark-solid';
12
+ case 'warning':
13
+ return 'icon-warning-solid';
14
+ default:
15
+ return 'icon-info-solid';
16
+ }
17
+ };
18
+ return (_jsxs("div", { className: cn('efp-alert', `efp-alert--${variant} efp-alert--${size}`, {
19
+ 'efp-alert--inline': inline,
20
+ [`efp-alert--${position}`]: position,
21
+ }), role: variant === 'error' || variant === 'warning' ? 'alert' : 'status', "aria-live": variant === 'error' || variant === 'warning' ? 'assertive' : 'polite', children: [showIcon && (_jsx("div", { className: "efp-alert__icon", children: _jsx("i", { className: cn(alertIcon()), "aria-hidden": "true" }) })), _jsxs("div", { className: "efp-alert__content", children: [title && _jsx("div", { className: "efp-alert__title", children: title }), children] }), closable && (_jsx("button", { type: "button", className: "efp-alert__close", onClick: onClose, "aria-label": closeLabel, title: closeLabel }))] }));
22
+ };
23
+ export default Alert;
@@ -0,0 +1,3 @@
1
+ export type { AlertPosition, AlertProps, AlertSize, AlertVariant } from './Alert';
2
+ export { default } from './Alert';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ export { default } from './Alert';
@@ -0,0 +1,28 @@
1
+ import './Autocomplete.scss';
2
+ import React from 'react';
3
+ export interface AutocompleteOptionObject {
4
+ value: string;
5
+ label: string;
6
+ }
7
+ export interface AutocompleteProps<T extends AutocompleteOptionObject = AutocompleteOptionObject> {
8
+ placeholder: string;
9
+ options: T[];
10
+ value?: T;
11
+ showClear?: boolean;
12
+ onChange?: (value: T | undefined) => void;
13
+ renderTarget?: HTMLElement | null;
14
+ /**
15
+ * Anchor the option list to the input with CSS instead of portalling it and
16
+ * measuring where it should go. The bottom sheet wants this: while the
17
+ * on-screen keyboard pans the viewport, measured coordinates are stale by the
18
+ * time they are written (#1567).
19
+ */
20
+ inline?: boolean;
21
+ noOptionsLabel: string;
22
+ ariaLabel: string;
23
+ clearLabel: string;
24
+ getOptionKey?: (option: T) => string;
25
+ }
26
+ declare const Autocomplete: <T extends AutocompleteOptionObject = AutocompleteOptionObject>({ placeholder, options, value, showClear, onChange, renderTarget, inline, noOptionsLabel, ariaLabel, clearLabel, getOptionKey, }: AutocompleteProps<T>) => React.JSX.Element;
27
+ export default Autocomplete;
28
+ //# sourceMappingURL=Autocomplete.d.ts.map
@@ -0,0 +1,292 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import './Autocomplete.scss';
3
+ import classNames from 'classnames';
4
+ import { lazy, Suspense, useContext, useEffect, useId, useMemo, useRef, useState, } from 'react';
5
+ import { createPortal } from 'react-dom';
6
+ import { ResizeObserver } from 'resize-observer';
7
+ import useOnClickOutside from '../../shared/hooks/useOnClickOutside';
8
+ import { OverlayKeyboardInsetContext } from '../Overlay/OverlayKeyboardInsetContext';
9
+ import { OverlayScrollContext } from '../Overlay/OverlayScrollContext';
10
+ const DROPDOWN_GAP = 4;
11
+ /** Below this the list is useless, so it is allowed to overflow the visible area. */
12
+ const DROPDOWN_MIN_HEIGHT = 96;
13
+ /** Share of the visible viewport the list may take (the SCSS counterpart of a `vh` cap). */
14
+ const DROPDOWN_HEIGHT_RATIO = 0.45;
15
+ const Autocomplete = ({ placeholder, options, value, showClear = false, onChange, renderTarget = document.body, inline = false, noOptionsLabel, ariaLabel, clearLabel, getOptionKey = (option) => option.value, }) => {
16
+ const container = renderTarget;
17
+ // The scrollable body of the sheet the inline list must stay inside of.
18
+ const scrollContainer = useContext(OverlayScrollContext);
19
+ const keyboardInset = useContext(OverlayKeyboardInsetContext);
20
+ const refAutocomplete = useRef(null);
21
+ const refSearchInput = useRef(null);
22
+ const refOptions = useRef(null);
23
+ const virtuosoRef = useRef(null);
24
+ // State, not a ref: the list can only mount once its scroll parent exists.
25
+ const [scrollElement, setScrollElement] = useState(null);
26
+ const blurTimeoutRef = useRef(null);
27
+ const listboxId = useId();
28
+ const getOptionId = (index) => `${listboxId}-option-${index}`;
29
+ const getOptionLabel = (option) => option.label;
30
+ const isSameOption = (a, b) => getOptionKey(a) === getOptionKey(b);
31
+ const findOptionIndexByValue = (searchValue) => options.findIndex((option) => isSameOption(option, searchValue));
32
+ const findOptionByValue = (searchValue) => options.find((option) => isSameOption(option, searchValue));
33
+ const [searchText, setSearchText] = useState(null);
34
+ const [focusOptionIndex, setFocusOptionIndex] = useState(null);
35
+ const [showOptionsDropdown, setShowOptionsDropdown] = useState(false);
36
+ const [inlineMaxHeight, setInlineMaxHeight] = useState(null);
37
+ const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0, width: 0, maxHeight: 0 });
38
+ const activeOption = value ? findOptionByValue(value) : undefined;
39
+ const activeOptionIndex = activeOption ? findOptionIndexByValue(activeOption) : null;
40
+ const displayValue = searchText ?? (activeOption ? getOptionLabel(activeOption) : '');
41
+ const filteredOptions = useMemo(() => {
42
+ if (!searchText)
43
+ return [];
44
+ const lower = searchText.toLowerCase();
45
+ return options.filter((option) => getOptionLabel(option).toLowerCase().includes(lower));
46
+ }, [searchText, options]);
47
+ const updateDropdownPosition = () => {
48
+ if (inline || !refAutocomplete.current || !showOptionsDropdown)
49
+ return;
50
+ const rect = refAutocomplete.current.getBoundingClientRect();
51
+ // The visible bounds, not the layout ones: the on-screen keyboard shrinks
52
+ // `visualViewport` while `innerHeight` stays put.
53
+ const viewport = typeof window.visualViewport !== 'undefined' ? window.visualViewport : null;
54
+ const visibleTop = viewport ? viewport.offsetTop : 0;
55
+ const visibleBottom = viewport ? viewport.offsetTop + viewport.height : window.innerHeight;
56
+ const spaceBelow = visibleBottom - rect.bottom - DROPDOWN_GAP;
57
+ const spaceAbove = rect.top - visibleTop - DROPDOWN_GAP;
58
+ // Flip up when the list does not fit below and above is roomier (#1567).
59
+ const desiredHeight = (visibleBottom - visibleTop) * DROPDOWN_HEIGHT_RATIO;
60
+ const openUp = spaceBelow < desiredHeight && spaceAbove > spaceBelow;
61
+ const space = Math.max(openUp ? spaceAbove : spaceBelow, DROPDOWN_MIN_HEIGHT);
62
+ const maxHeight = Math.min(desiredHeight, space);
63
+ // The dropdown is absolutely positioned inside the render target, so both
64
+ // edges are measured against it — never against the document.
65
+ const containerRect = container?.getBoundingClientRect();
66
+ const containerTop = containerRect?.top ?? 0;
67
+ setDropdownPosition({
68
+ ...(openUp
69
+ ? { bottom: (containerRect?.bottom ?? window.innerHeight) - rect.top + DROPDOWN_GAP }
70
+ : { top: rect.bottom - containerTop + DROPDOWN_GAP }),
71
+ left: rect.left - (containerRect?.left ?? 0),
72
+ width: rect.width,
73
+ maxHeight,
74
+ });
75
+ };
76
+ useEffect(() => {
77
+ if (inline || !showOptionsDropdown)
78
+ return;
79
+ updateDropdownPosition();
80
+ window.addEventListener('scroll', updateDropdownPosition, true);
81
+ window.addEventListener('resize', updateDropdownPosition);
82
+ const viewport = typeof window.visualViewport !== 'undefined' ? window.visualViewport : null;
83
+ viewport?.addEventListener('resize', updateDropdownPosition);
84
+ viewport?.addEventListener('scroll', updateDropdownPosition);
85
+ return () => {
86
+ window.removeEventListener('scroll', updateDropdownPosition, true);
87
+ window.removeEventListener('resize', updateDropdownPosition);
88
+ viewport?.removeEventListener('resize', updateDropdownPosition);
89
+ viewport?.removeEventListener('scroll', updateDropdownPosition);
90
+ };
91
+ }, [showOptionsDropdown, inline]);
92
+ // The inline list ends where the sheet's scroll body does, or at the keyboard
93
+ // when that body runs on under one. Left to overflow, it would extend that
94
+ // body and the tail could only be reached by scrolling the sheet, then the
95
+ // list. Both rects come from the same layout, so the keyboard panning the
96
+ // viewport does not skew the difference.
97
+ useEffect(() => {
98
+ if (!inline || !showOptionsDropdown || !scrollContainer) {
99
+ setInlineMaxHeight(null);
100
+ return;
101
+ }
102
+ const measure = () => {
103
+ const anchor = refAutocomplete.current;
104
+ if (!anchor)
105
+ return;
106
+ const room = scrollContainer.getBoundingClientRect().bottom -
107
+ keyboardInset -
108
+ anchor.getBoundingClientRect().bottom -
109
+ DROPDOWN_GAP;
110
+ setInlineMaxHeight(Math.max(room, DROPDOWN_MIN_HEIGHT));
111
+ };
112
+ measure();
113
+ // The sheet resizes under the list: snap points, the keyboard, a drag.
114
+ const observer = new ResizeObserver(measure);
115
+ observer.observe(scrollContainer);
116
+ return () => observer.disconnect();
117
+ }, [inline, showOptionsDropdown, scrollContainer, keyboardInset]);
118
+ useEffect(() => {
119
+ if (focusOptionIndex === null)
120
+ return;
121
+ // Through the list, not the DOM: a focused option far down is not rendered until scrolled to.
122
+ virtuosoRef.current?.scrollIntoView({ index: focusOptionIndex });
123
+ }, [focusOptionIndex]);
124
+ useOnClickOutside([refAutocomplete, refOptions], () => setShowOptionsDropdown(false), showOptionsDropdown);
125
+ useEffect(() => {
126
+ return () => {
127
+ if (blurTimeoutRef.current)
128
+ clearTimeout(blurTimeoutRef.current);
129
+ };
130
+ }, []);
131
+ const updateValue = (newValue) => {
132
+ setSearchText(null);
133
+ onChange?.(newValue);
134
+ };
135
+ const selectOption = (option) => {
136
+ updateValue(option);
137
+ setFocusOptionIndex(null);
138
+ setShowOptionsDropdown(false);
139
+ refSearchInput.current?.blur();
140
+ };
141
+ const clearValue = (setFocus = false) => {
142
+ updateValue(undefined);
143
+ setFocusOptionIndex(null);
144
+ if (setFocus)
145
+ refSearchInput.current?.focus();
146
+ };
147
+ const onFocus = () => {
148
+ setShowOptionsDropdown(true);
149
+ };
150
+ const onBlur = () => {
151
+ if (blurTimeoutRef.current)
152
+ clearTimeout(blurTimeoutRef.current);
153
+ blurTimeoutRef.current = setTimeout(() => {
154
+ setSearchText(null);
155
+ }, 200);
156
+ };
157
+ const handleEnterKey = () => {
158
+ if ((!displayValue && focusOptionIndex === null) || (searchText && focusOptionIndex === null)) {
159
+ return;
160
+ }
161
+ const currentOptions = filteredOptions.length > 0 ? filteredOptions : options;
162
+ if (focusOptionIndex !== null && currentOptions[focusOptionIndex]) {
163
+ const selectedOption = currentOptions[focusOptionIndex];
164
+ updateValue(selectedOption);
165
+ }
166
+ setFocusOptionIndex(null);
167
+ setShowOptionsDropdown(false);
168
+ refSearchInput.current?.blur();
169
+ };
170
+ const handleArrowUp = () => {
171
+ if (!showOptionsDropdown || focusOptionIndex === 0 || focusOptionIndex === null) {
172
+ return;
173
+ }
174
+ setFocusOptionIndex(focusOptionIndex - 1);
175
+ };
176
+ const handleArrowDown = () => {
177
+ if (!showOptionsDropdown) {
178
+ setFocusOptionIndex(activeOptionIndex ?? 0);
179
+ setShowOptionsDropdown(true);
180
+ return;
181
+ }
182
+ const currentOptions = filteredOptions.length > 0 ? filteredOptions : options;
183
+ const nextIndex = focusOptionIndex === null ? 0 : focusOptionIndex + 1;
184
+ if (nextIndex < currentOptions.length) {
185
+ setFocusOptionIndex(nextIndex);
186
+ }
187
+ };
188
+ const onKeyDown = (event) => {
189
+ switch (event.key) {
190
+ case 'Enter':
191
+ handleEnterKey();
192
+ break;
193
+ case 'ArrowUp':
194
+ handleArrowUp();
195
+ break;
196
+ case 'ArrowDown':
197
+ handleArrowDown();
198
+ break;
199
+ case 'Delete':
200
+ clearValue(true);
201
+ break;
202
+ case 'Escape':
203
+ if (!searchText) {
204
+ event.preventDefault();
205
+ setShowOptionsDropdown(false);
206
+ refSearchInput.current?.blur();
207
+ }
208
+ else {
209
+ setSearchText(null);
210
+ setFocusOptionIndex(activeOptionIndex);
211
+ }
212
+ break;
213
+ }
214
+ };
215
+ const onInputChange = (event) => {
216
+ setSearchText(event.target.value);
217
+ setShowOptionsDropdown(true);
218
+ setFocusOptionIndex(null);
219
+ };
220
+ const renderOptions = () => {
221
+ const currentOptions = filteredOptions.length > 0 ? filteredOptions : options;
222
+ if (searchText && filteredOptions.length === 0) {
223
+ return _jsx("div", { className: "efp-autocomplete__empty", children: noOptionsLabel });
224
+ }
225
+ // Virtualized: a plan's booth list runs to thousands of options, and rendering, laying out and
226
+ // compositing every one of them froze the frame the dropdown opened in for over half a second.
227
+ if (!scrollElement)
228
+ return null;
229
+ const context = {
230
+ listboxId,
231
+ focusIndex: focusOptionIndex,
232
+ activeIndex: filteredOptions.length === 0 ? activeOptionIndex : null,
233
+ onSelect: (index) => selectOption(currentOptions[index]),
234
+ };
235
+ return (_jsx(Suspense, { fallback: null, children: _jsx(LazyVirtuoso, { ref: virtuosoRef, customScrollParent: scrollElement, data: currentOptions, context: context, computeItemKey: (_index, option) => getOptionKey(option), itemContent: (_index, option) => getOptionLabel(option), components: { List: OptionList, Item: OptionItem }, increaseViewportBy: 200 }) }));
236
+ };
237
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { ref: refAutocomplete, "data-no-drag": true, className: classNames('efp-autocomplete', {
238
+ 'is-open': showOptionsDropdown,
239
+ 'with-clear': showOptionsDropdown && showClear,
240
+ }), children: [_jsxs("div", { className: "efp-autocomplete__inner", children: [_jsx("input", { type: "search", className: "efp-autocomplete__input", role: "combobox", "aria-label": ariaLabel, "aria-autocomplete": "list", "aria-expanded": showOptionsDropdown, "aria-controls": showOptionsDropdown ? listboxId : undefined, "aria-activedescendant": showOptionsDropdown && focusOptionIndex !== null
241
+ ? getOptionId(focusOptionIndex)
242
+ : undefined, onChange: onInputChange, onFocus: onFocus, onBlur: onBlur, onKeyDown: onKeyDown, value: displayValue, placeholder: placeholder, ref: refSearchInput }), showClear && showOptionsDropdown && displayValue && (_jsx("button", { type: "button", className: "efp-autocomplete__clear", "aria-label": clearLabel, onMouseDown: (e) => {
243
+ e.preventDefault();
244
+ clearValue(true);
245
+ } }))] }), showOptionsDropdown && inline && (_jsx("div", { ref: refOptions, "data-no-drag": true, className: "efp-autocomplete-options efp-autocomplete-options--inline", style: inlineMaxHeight !== null ? { maxHeight: `${inlineMaxHeight}px` } : undefined, children: _jsx("div", { className: "efp-autocomplete-options__scroll", ref: setScrollElement, children: renderOptions() }) }))] }), showOptionsDropdown &&
246
+ !inline &&
247
+ container &&
248
+ createPortal(_jsx("div", { ref: refOptions, className: "efp-autocomplete-options", style: {
249
+ ...(dropdownPosition.top !== undefined ? { top: `${dropdownPosition.top}px` } : {}),
250
+ ...(dropdownPosition.bottom !== undefined
251
+ ? { bottom: `${dropdownPosition.bottom}px` }
252
+ : {}),
253
+ left: `${dropdownPosition.left}px`,
254
+ width: `${dropdownPosition.width}px`,
255
+ maxHeight: `${dropdownPosition.maxHeight}px`,
256
+ }, children: _jsx("div", { className: "efp-autocomplete-options__scroll", ref: setScrollElement, children: renderOptions() }) }), container)] }));
257
+ };
258
+ /**
259
+ * The listbox itself; Virtuoso positions it inside the scroll parent.
260
+ * @param props - Virtuoso's list props, plus the autocomplete's context.
261
+ * @returns The `ul`.
262
+ */
263
+ function OptionList({ context, ref, ...props }) {
264
+ // Virtuoso types its list element as a `div`; the listbox is a `ul`, which it handles the same.
265
+ return (_jsx("ul", { ...props, ref: ref, role: "listbox", id: context.listboxId }));
266
+ }
267
+ /**
268
+ * One option; Virtuoso renders only the ones in and near view.
269
+ * @param props - Virtuoso's item props, plus the autocomplete's context.
270
+ * @returns The `li`.
271
+ */
272
+ function OptionItem({ context, item: _item, ...props }) {
273
+ const index = props['data-index'];
274
+ const isFocused = index === context.focusIndex;
275
+ const isActive = !isFocused && index === context.activeIndex;
276
+ return (_jsx("li", { ...props, id: `${context.listboxId}-option-${index}`, role: "option", "aria-selected": index === context.activeIndex, className: isFocused ? 'is-focus' : isActive ? 'is-active' : undefined, onMouseDown: (e) => e.preventDefault(), onClick: () => context.onSelect(index) }));
277
+ }
278
+ /**
279
+ * Loads Virtuoso on first open rather than with the SDK's main chunk, as the floorplan's own lists
280
+ * do; the retry covers a chunk request lost to a flaky network.
281
+ * @returns The module, shaped for `lazy`.
282
+ */
283
+ function importVirtuoso() {
284
+ return import('react-virtuoso')
285
+ .then((mod) => ({ default: mod.Virtuoso }))
286
+ .catch(() => new Promise((resolve) => {
287
+ setTimeout(() => resolve(importVirtuoso()), 3000);
288
+ }));
289
+ }
290
+ // Typed as the generic component it resolves to; `lazy` alone erases the data and context types.
291
+ const LazyVirtuoso = lazy(importVirtuoso);
292
+ export default Autocomplete;
@@ -0,0 +1,3 @@
1
+ export type { AutocompleteOptionObject, AutocompleteProps } from './Autocomplete';
2
+ export { default } from './Autocomplete';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ export { default } from './Autocomplete';
@@ -0,0 +1,14 @@
1
+ import './Badge.scss';
2
+ import React, { type ReactNode } from 'react';
3
+ export type BadgeVariant = 'primary' | 'primary-light' | 'lightgray' | 'gray' | 'ghost' | 'orange' | 'location';
4
+ export interface BadgeProps {
5
+ children?: ReactNode;
6
+ variant?: BadgeVariant;
7
+ size?: 'xs' | 'md' | 'lg';
8
+ noMargins?: boolean;
9
+ rounded?: boolean;
10
+ inline?: boolean;
11
+ }
12
+ declare const Badge: ({ children, variant, size, noMargins, rounded, inline, }: BadgeProps) => React.JSX.Element;
13
+ export default Badge;
14
+ //# sourceMappingURL=Badge.d.ts.map
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // @ts-nocheck
3
+ import './Badge.scss';
4
+ import cn from 'classnames';
5
+ const Badge = ({ children, variant = 'ghost', size = 'lg', noMargins, rounded, inline, }) => {
6
+ return (_jsx("div", { className: cn({
7
+ 'efp-badge': true,
8
+ [`efp-badge--${variant}`]: true,
9
+ [`efp-badge--${size}`]: true,
10
+ 'is-no-margin': noMargins,
11
+ 'is-rounded': rounded,
12
+ 'is-inline': inline,
13
+ }), children: children }));
14
+ };
15
+ export default Badge;
@@ -0,0 +1,3 @@
1
+ export type { BadgeProps, BadgeVariant } from './Badge';
2
+ export { default } from './Badge';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ export { default } from './Badge';
@@ -0,0 +1,30 @@
1
+ import './Button.scss';
2
+ import React, { type ReactNode } from 'react';
3
+ export type ButtonVariant = 'primary' | 'secondary' | 'gray' | 'gray-border' | 'ghost' | 'danger';
4
+ export type ButtonSize = 'sm' | 'md' | 'lg';
5
+ export type ButtonTarget = '_self' | '_blank' | '_parent';
6
+ interface ButtonOwnProps {
7
+ children?: ReactNode;
8
+ inline?: boolean;
9
+ text?: string;
10
+ disabled?: boolean;
11
+ variant?: ButtonVariant;
12
+ size?: ButtonSize;
13
+ target?: ButtonTarget;
14
+ ariaLabel?: string;
15
+ withBadge?: boolean;
16
+ /** Whether the button should have a round/circular shape */
17
+ round?: boolean;
18
+ }
19
+ type ButtonAsButton = ButtonOwnProps & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, keyof ButtonOwnProps> & {
20
+ link?: undefined;
21
+ ref?: React.Ref<HTMLButtonElement>;
22
+ };
23
+ type ButtonAsLink = ButtonOwnProps & Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, keyof ButtonOwnProps> & {
24
+ link: string;
25
+ ref?: React.Ref<HTMLAnchorElement>;
26
+ };
27
+ export type ButtonProps = ButtonAsButton | ButtonAsLink;
28
+ declare const Button: (props: ButtonProps) => React.JSX.Element;
29
+ export default Button;
30
+ //# sourceMappingURL=Button.d.ts.map
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import './Button.scss';
3
+ import cn from 'classnames';
4
+ // `if (props.link)` alone doesn't narrow the union for TS; this predicate does.
5
+ const isLinkProps = (props) => props.link != null;
6
+ const processLabel = (label) => {
7
+ const parts = label.split(/(#\d+#)/);
8
+ const badgeMatch = /#(\d+)#/.exec(label);
9
+ const badgeValue = badgeMatch ? badgeMatch[1] : null;
10
+ return {
11
+ content: parts.map((part, index) => {
12
+ if (/#\d+#/.exec(part)) {
13
+ return (_jsx("div", { className: "efp-button-badge", children: badgeValue }, index));
14
+ }
15
+ return _jsx("span", { children: part }, index);
16
+ }),
17
+ };
18
+ };
19
+ const Button = (props) => {
20
+ const { children, inline = false, text, disabled = false, variant = 'primary', size = 'lg', target = '_self', ariaLabel, title, withBadge = false, round, } = props;
21
+ const content = children ?? text;
22
+ const { content: processedContent } = withBadge && typeof content === 'string' ? processLabel(content) : { content };
23
+ const commonClassNames = cn('efp-button', `efp-button--${variant}`, `efp-button--${size}`, {
24
+ 'efp-button--inline': inline,
25
+ 'is-disabled': disabled,
26
+ 'efp-button--round': round,
27
+ }, props.className);
28
+ if (isLinkProps(props)) {
29
+ const { link, className: _className, onClick, ...rest } = props;
30
+ const onKeyDown = (event) => {
31
+ if (disabled || event.key !== ' ')
32
+ return;
33
+ event.preventDefault();
34
+ event.currentTarget.click();
35
+ };
36
+ return (_jsx("a", { ...rest, href: disabled ? undefined : link, className: commonClassNames, target: target, rel: "noopener noreferrer", onClick: disabled ? undefined : onClick, onKeyDown: onKeyDown, "aria-label": ariaLabel || undefined, title: title || ariaLabel || (typeof content === 'string' ? content : undefined), role: "button", "aria-disabled": disabled, tabIndex: disabled ? -1 : 0, children: processedContent }));
37
+ }
38
+ const { className: _className, ...rest } = props;
39
+ return (_jsx("button", { type: "button", ...rest, className: commonClassNames, disabled: disabled, "aria-label": ariaLabel || undefined, title: title || ariaLabel || (typeof content === 'string' ? content : undefined), children: processedContent }));
40
+ };
41
+ export default Button;
@@ -0,0 +1,3 @@
1
+ export type { ButtonProps, ButtonSize, ButtonTarget, ButtonVariant } from './Button';
2
+ export { default } from './Button';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ export { default } from './Button';
@@ -0,0 +1,19 @@
1
+ import './Checkbox.scss';
2
+ import React from 'react';
3
+ interface CheckboxBase {
4
+ name?: string;
5
+ value: boolean;
6
+ indeterminate?: boolean;
7
+ size?: 'sm' | 'md';
8
+ onChange: (value: boolean) => void;
9
+ }
10
+ export type CheckboxProps = (CheckboxBase & {
11
+ label: string;
12
+ ariaLabel?: string;
13
+ }) | (CheckboxBase & {
14
+ label?: undefined;
15
+ ariaLabel: string;
16
+ });
17
+ declare const Checkbox: ({ name, value, label, ariaLabel, size, indeterminate, onChange, }: CheckboxProps) => React.JSX.Element;
18
+ export default Checkbox;
19
+ //# sourceMappingURL=Checkbox.d.ts.map
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // @ts-nocheck
3
+ import './Checkbox.scss';
4
+ import classNames from 'classnames';
5
+ import { useEffect, useId, useRef } from 'react';
6
+ const Checkbox = ({ name, value, label, ariaLabel, size = 'md', indeterminate = false, onChange, }) => {
7
+ const id = useId();
8
+ const checkboxRef = useRef(null);
9
+ useEffect(() => {
10
+ if (checkboxRef.current) {
11
+ checkboxRef.current.indeterminate = indeterminate;
12
+ }
13
+ }, [indeterminate]);
14
+ return (_jsxs("div", { className: classNames({
15
+ 'efp-checkbox': true,
16
+ [`efp-checkbox--${size}`]: true,
17
+ }), children: [_jsx("input", { ref: checkboxRef, type: "checkbox", id: id, name: name, checked: value, "aria-label": ariaLabel, onChange: () => onChange(!value) }), _jsx("label", { htmlFor: id, children: label })] }));
18
+ };
19
+ export default Checkbox;
@@ -0,0 +1,3 @@
1
+ export type { CheckboxProps } from './Checkbox';
2
+ export { default } from './Checkbox';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ export { default } from './Checkbox';
@@ -0,0 +1,12 @@
1
+ import './CheckboxButton.scss';
2
+ import React from 'react';
3
+ export interface CheckboxButtonProps {
4
+ checked: boolean;
5
+ label?: string;
6
+ ariaLabel?: string;
7
+ className?: string;
8
+ onClick: () => void;
9
+ }
10
+ declare const CheckboxButton: ({ checked, label, ariaLabel, className, onClick }: CheckboxButtonProps) => React.JSX.Element;
11
+ export default CheckboxButton;
12
+ //# sourceMappingURL=CheckboxButton.d.ts.map
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // @ts-nocheck
3
+ import './CheckboxButton.scss';
4
+ import cn from 'classnames';
5
+ import { useState } from 'react';
6
+ const CheckboxButton = ({ checked, label, ariaLabel, className, onClick }) => {
7
+ const [animateIcon, setAnimateIcon] = useState(false);
8
+ const handleClick = () => {
9
+ setAnimateIcon(true);
10
+ onClick();
11
+ setTimeout(() => {
12
+ setAnimateIcon(false);
13
+ }, 320);
14
+ };
15
+ return (_jsxs("button", { type: "button", role: "checkbox", "aria-checked": checked, "aria-label": ariaLabel ?? (label ? undefined : 'Toggle'), className: cn('efp-checkbox-button', className, { 'efp-checkbox-button--checked': checked }), onClick: handleClick, children: [_jsx("div", { className: cn('efp-checkbox-button__inner', {
16
+ 'efp-checkbox-button__inner--animate': animateIcon,
17
+ }) }), label && _jsx("span", { className: "efp-checkbox-button__label", children: label })] }));
18
+ };
19
+ export default CheckboxButton;
@@ -0,0 +1,3 @@
1
+ export type { CheckboxButtonProps } from './CheckboxButton';
2
+ export { default } from './CheckboxButton';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ export { default } from './CheckboxButton';
@@ -0,0 +1,15 @@
1
+ import './DownloadList.scss';
2
+ export interface DownloadItem {
3
+ fileName: string;
4
+ path: string;
5
+ }
6
+ export interface DownloadListProps {
7
+ list: DownloadItem[];
8
+ /** Section heading (e.g. a localized "Read More"). */
9
+ title: string;
10
+ /** Builds the per-item download link accessible name from its file name. */
11
+ downloadLabel: (fileName: string) => string;
12
+ }
13
+ declare function DownloadList({ list, title, downloadLabel }: DownloadListProps): import("react").JSX.Element;
14
+ export default DownloadList;
15
+ //# sourceMappingURL=DownloadList.d.ts.map