@12-apps/ui 6.39.10 → 6.39.12

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 (60) hide show
  1. package/dist/_internal/components/data-display/DataViews/DataViewsTableBase.js +4 -4
  2. package/dist/_internal/components/data-display/DataViews/SaveViewModal.js +2 -2
  3. package/dist/_internal/components/data-display/DataViews/data-views-table-parts.js +3 -3
  4. package/dist/_internal/components/data-display/HoverCard/HoverCard.hooks.js +1 -1
  5. package/dist/_internal/components/data-display/HoverCard/HoverCard.js +2 -2
  6. package/dist/_internal/components/data-display/index.js +2 -2
  7. package/dist/_internal/components/form/CepField/CepField.js +2 -2
  8. package/dist/_internal/components/form/Input/Input.js +1 -1
  9. package/dist/_internal/components/form/RichTextEditor/RichTextEditor.hooks.js +1 -1
  10. package/dist/_internal/components/form/RichTextEditor/RichTextEditor.js +2 -2
  11. package/dist/_internal/components/form/index.js +4 -4
  12. package/dist/_internal/components/form/total-form/Fields/CepField.js +3 -3
  13. package/dist/_internal/components/form/total-form/Fields/TextField.js +2 -2
  14. package/dist/_internal/components/form/total-form/Fields/index.js +5 -5
  15. package/dist/_internal/components/index.js +6 -6
  16. package/dist/{chunk-F6D6HXZU.js → chunk-7X7ZHJHM.js} +2 -2
  17. package/dist/{chunk-DAHYLPK7.js → chunk-CMV6BRQ3.js} +13 -4
  18. package/dist/chunk-CMV6BRQ3.js.map +1 -0
  19. package/dist/{chunk-774B27QA.js → chunk-EUACQR6N.js} +4 -3
  20. package/dist/chunk-EUACQR6N.js.map +1 -0
  21. package/dist/{chunk-YS67XMOP.js → chunk-I5ZVCY2N.js} +3 -3
  22. package/dist/{chunk-A5B7B4HT.js → chunk-INMKA5SF.js} +2 -2
  23. package/dist/{chunk-VJG7NMGL.js → chunk-IYF246UA.js} +2 -2
  24. package/dist/{chunk-VURQZBFH.js → chunk-JRJCBDGN.js} +16 -3
  25. package/dist/chunk-JRJCBDGN.js.map +1 -0
  26. package/dist/{chunk-7EM4THID.js → chunk-RTEY5COP.js} +2 -2
  27. package/dist/{chunk-2XX45PC5.js → chunk-TGXCG62P.js} +2 -2
  28. package/dist/{chunk-SXYIXCUB.js → chunk-VNWZWQF3.js} +41 -23
  29. package/dist/chunk-VNWZWQF3.js.map +1 -0
  30. package/dist/{chunk-SSS64KYB.js → chunk-Z43N7ASP.js} +2 -2
  31. package/dist/{chunk-6MFMLQ72.js → chunk-ZNDDILNQ.js} +2 -2
  32. package/dist/data-display/DataViews.js +4 -4
  33. package/dist/data-display/HoverCard.js +2 -2
  34. package/dist/form/CepField.js +2 -2
  35. package/dist/form/Input.js +1 -1
  36. package/dist/form/RichTextEditor.js +2 -2
  37. package/dist/form/total-form.js +5 -5
  38. package/dist/types/components/data-display/HoverCard/HoverCard.d.ts.map +1 -1
  39. package/dist/types/components/data-display/HoverCard/HoverCard.hooks.d.ts +1 -0
  40. package/dist/types/components/data-display/HoverCard/HoverCard.hooks.d.ts.map +1 -1
  41. package/dist/types/components/form/Input/Input.d.ts.map +1 -1
  42. package/dist/types/components/form/RichTextEditor/RichTextEditor.hooks.d.ts.map +1 -1
  43. package/package.json +1 -1
  44. package/src/components/data-display/HoverCard/HoverCard.hooks.ts +80 -25
  45. package/src/components/data-display/HoverCard/HoverCard.md +1 -0
  46. package/src/components/data-display/HoverCard/HoverCard.tsx +11 -2
  47. package/src/components/form/Input/Input.tsx +40 -1
  48. package/src/components/form/RichTextEditor/RichTextEditor.hooks.ts +20 -3
  49. package/dist/chunk-774B27QA.js.map +0 -1
  50. package/dist/chunk-DAHYLPK7.js.map +0 -1
  51. package/dist/chunk-SXYIXCUB.js.map +0 -1
  52. package/dist/chunk-VURQZBFH.js.map +0 -1
  53. /package/dist/{chunk-F6D6HXZU.js.map → chunk-7X7ZHJHM.js.map} +0 -0
  54. /package/dist/{chunk-YS67XMOP.js.map → chunk-I5ZVCY2N.js.map} +0 -0
  55. /package/dist/{chunk-A5B7B4HT.js.map → chunk-INMKA5SF.js.map} +0 -0
  56. /package/dist/{chunk-VJG7NMGL.js.map → chunk-IYF246UA.js.map} +0 -0
  57. /package/dist/{chunk-7EM4THID.js.map → chunk-RTEY5COP.js.map} +0 -0
  58. /package/dist/{chunk-2XX45PC5.js.map → chunk-TGXCG62P.js.map} +0 -0
  59. /package/dist/{chunk-SSS64KYB.js.map → chunk-Z43N7ASP.js.map} +0 -0
  60. /package/dist/{chunk-6MFMLQ72.js.map → chunk-ZNDDILNQ.js.map} +0 -0
@@ -1,5 +1,9 @@
1
1
  // src/components/data-display/HoverCard/HoverCard.hooks.ts
2
2
  import React from "react";
3
+ var clearPending = (ref) => {
4
+ if (ref.current !== void 0) window.clearTimeout(ref.current);
5
+ ref.current = void 0;
6
+ };
3
7
  var useHoverHandlers = ({
4
8
  isTouchDevice,
5
9
  touchEnabled,
@@ -25,7 +29,7 @@ var useHoverHandlers = ({
25
29
  const triggerHandlers = {
26
30
  onMouseEnter: (event) => {
27
31
  if (hoverIgnored) return;
28
- openCard(event);
32
+ openCard(event.currentTarget);
29
33
  },
30
34
  onMouseLeave: () => {
31
35
  if (hoverIgnored) return;
@@ -34,16 +38,15 @@ var useHoverHandlers = ({
34
38
  onTouchStart: (event) => {
35
39
  if (!touchEnabled) return;
36
40
  if (touchTimeout) window.clearTimeout(touchTimeout);
37
- setTouchTimeout(window.setTimeout(() => openCard(event), enterDelay));
41
+ const trigger = event.currentTarget;
42
+ setTouchTimeout(window.setTimeout(() => openCard(trigger), enterDelay));
38
43
  },
39
44
  // End and cancel do the same thing: the press did not become a long press.
40
45
  onTouchEnd: cancelTouch,
41
46
  onTouchCancel: cancelTouch
42
47
  };
43
48
  const cardHandlers = {
44
- onMouseEnter: () => {
45
- if (exitTimeoutRef.current) window.clearTimeout(exitTimeoutRef.current);
46
- },
49
+ onMouseEnter: () => clearPending(exitTimeoutRef),
47
50
  onMouseLeave: closeCard
48
51
  };
49
52
  return { triggerHandlers, cardHandlers };
@@ -58,6 +61,25 @@ var useEscapeKey = (active, onEscape) => {
58
61
  return () => document.removeEventListener("keydown", handleEscape);
59
62
  }, [active, onEscape]);
60
63
  };
64
+ var useClickAway = (active, inside, onAway) => {
65
+ React.useEffect(() => {
66
+ if (!active) return;
67
+ const handlePointerDown = (event) => {
68
+ const target = event.target;
69
+ if (target && inside().some((el) => el?.contains(target))) return;
70
+ onAway();
71
+ };
72
+ document.addEventListener("pointerdown", handlePointerDown, true);
73
+ return () => document.removeEventListener("pointerdown", handlePointerDown, true);
74
+ }, [active, inside, onAway]);
75
+ };
76
+ var useClearOnUnmount = (...refs) => {
77
+ React.useEffect(
78
+ () => () => refs.forEach(clearPending),
79
+ // The refs are stable objects: a mount/unmount pair, as before.
80
+ []
81
+ );
82
+ };
61
83
  var useHoverCard = ({
62
84
  disabled,
63
85
  touchEnabled,
@@ -71,18 +93,17 @@ var useHoverCard = ({
71
93
  const [isTouchDevice, setIsTouchDevice] = React.useState(false);
72
94
  const enterTimeoutRef = React.useRef(void 0);
73
95
  const exitTimeoutRef = React.useRef(void 0);
96
+ const contentRef = React.useRef(null);
74
97
  React.useEffect(() => {
75
98
  setIsTouchDevice("ontouchstart" in window);
76
99
  }, []);
77
100
  const openCard = React.useCallback(
78
- (event) => {
101
+ (trigger) => {
79
102
  if (disabled) return;
80
- if (exitTimeoutRef.current) {
81
- window.clearTimeout(exitTimeoutRef.current);
82
- }
83
- const target = event.currentTarget;
103
+ clearPending(exitTimeoutRef);
104
+ clearPending(enterTimeoutRef);
84
105
  enterTimeoutRef.current = window.setTimeout(() => {
85
- setAnchorEl(target);
106
+ setAnchorEl(trigger);
86
107
  setIsOpen(true);
87
108
  onOpen?.();
88
109
  }, enterDelay);
@@ -90,9 +111,8 @@ var useHoverCard = ({
90
111
  [disabled, enterDelay, onOpen]
91
112
  );
92
113
  const closeCard = React.useCallback(() => {
93
- if (enterTimeoutRef.current) {
94
- window.clearTimeout(enterTimeoutRef.current);
95
- }
114
+ clearPending(enterTimeoutRef);
115
+ clearPending(exitTimeoutRef);
96
116
  exitTimeoutRef.current = window.setTimeout(() => {
97
117
  setIsOpen(false);
98
118
  setAnchorEl(null);
@@ -100,6 +120,8 @@ var useHoverCard = ({
100
120
  }, exitDelay);
101
121
  }, [exitDelay, onClose]);
102
122
  const handleClose = React.useCallback(() => {
123
+ clearPending(enterTimeoutRef);
124
+ clearPending(exitTimeoutRef);
103
125
  setIsOpen(false);
104
126
  setAnchorEl(null);
105
127
  onClose?.();
@@ -112,18 +134,14 @@ var useHoverCard = ({
112
134
  closeCard,
113
135
  exitTimeoutRef
114
136
  });
137
+ const insideCard = React.useCallback(() => [anchorEl, contentRef.current], [anchorEl]);
115
138
  useEscapeKey(isOpen, handleClose);
116
- React.useEffect(
117
- () => () => {
118
- if (enterTimeoutRef.current) window.clearTimeout(enterTimeoutRef.current);
119
- if (exitTimeoutRef.current) window.clearTimeout(exitTimeoutRef.current);
120
- },
121
- []
122
- );
123
- return { anchorEl, isOpen, handleClose, triggerHandlers, cardHandlers };
139
+ useClickAway(isOpen, insideCard, handleClose);
140
+ useClearOnUnmount(enterTimeoutRef, exitTimeoutRef);
141
+ return { anchorEl, isOpen, handleClose, triggerHandlers, cardHandlers, contentRef };
124
142
  };
125
143
 
126
144
  export {
127
145
  useHoverCard
128
146
  };
129
- //# sourceMappingURL=chunk-SXYIXCUB.js.map
147
+ //# sourceMappingURL=chunk-VNWZWQF3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/data-display/HoverCard/HoverCard.hooks.ts"],"sourcesContent":["import React from 'react';\n\ntype TimerRef = React.MutableRefObject<number | undefined>;\n\n/**\n * Cancel the timer a ref holds, and forget it. Every place that schedules\n * or dismisses goes through this, so no timer outlives the ref that could\n * cancel it (FUT-2619: an overwritten enter timer reopened a dismissed card).\n */\nconst clearPending = (ref: TimerRef) => {\n if (ref.current !== undefined) window.clearTimeout(ref.current);\n ref.current = undefined;\n};\n\ninterface HandlerInput {\n isTouchDevice: boolean;\n touchEnabled: boolean;\n enterDelay: number;\n openCard: (trigger: HTMLElement) => void;\n closeCard: () => void;\n exitTimeoutRef: TimerRef;\n}\n\n/**\n * The pointer and touch handlers, over the open/close pair. Touch gets a\n * long-press because there is no hover to respond to; the card's own handlers\n * keep it open while the pointer is travelling onto it.\n */\nconst useHoverHandlers = ({\n isTouchDevice,\n touchEnabled,\n enterDelay,\n openCard,\n closeCard,\n exitTimeoutRef,\n}: HandlerInput) => {\n const [touchTimeout, setTouchTimeout] = React.useState<number>();\n\n /** A touch device with touch disabled has no hover to respond to. */\n const hoverIgnored = isTouchDevice && !touchEnabled;\n\n const cancelTouch = React.useCallback(() => {\n if (touchTimeout) {\n window.clearTimeout(touchTimeout);\n setTouchTimeout(undefined);\n }\n }, [touchTimeout]);\n\n React.useEffect(\n () => () => {\n if (touchTimeout) window.clearTimeout(touchTimeout);\n },\n [touchTimeout],\n );\n\n const triggerHandlers = {\n onMouseEnter: (event: React.MouseEvent<HTMLElement>) => {\n if (hoverIgnored) return;\n openCard(event.currentTarget);\n },\n onMouseLeave: () => {\n if (hoverIgnored) return;\n closeCard();\n },\n onTouchStart: (event: React.TouchEvent<HTMLElement>) => {\n if (!touchEnabled) return;\n if (touchTimeout) window.clearTimeout(touchTimeout);\n // Read NOW: React nulls `currentTarget` once the handler returns, so\n // the long-press timer used to open the card with no anchor at all.\n const trigger = event.currentTarget;\n setTouchTimeout(window.setTimeout(() => openCard(trigger), enterDelay));\n },\n // End and cancel do the same thing: the press did not become a long press.\n onTouchEnd: cancelTouch,\n onTouchCancel: cancelTouch,\n };\n\n /** Moving onto the card keeps it open; leaving it starts the exit delay. */\n const cardHandlers = {\n onMouseEnter: () => clearPending(exitTimeoutRef),\n onMouseLeave: closeCard,\n };\n\n return { triggerHandlers, cardHandlers };\n};\n\n/** Escape dismisses the card while it is open. */\nconst useEscapeKey = (active: boolean, onEscape: () => void) => {\n React.useEffect(() => {\n if (!active) return;\n\n const handleEscape = (event: globalThis.KeyboardEvent) => {\n if (event.key === 'Escape') onEscape();\n };\n\n document.addEventListener('keydown', handleEscape);\n return () => document.removeEventListener('keydown', handleEscape);\n }, [active, onEscape]);\n};\n\n/**\n * A press anywhere outside the trigger and the card dismisses it.\n *\n * Touch has no mouse-leave, and MUI's own click-away was the popover's\n * backdrop, which `pointer-events: none` on the popover root (so the page\n * under an open card stays usable) takes out of play. So this listens on the\n * document itself, in the CAPTURE phase, where a `stopPropagation` further in\n * cannot swallow it. It only observes: no `preventDefault`, so the tap or\n * click still reaches whatever it landed on. Attached while open, removed on\n * close and on unmount.\n */\nconst useClickAway = (\n active: boolean,\n inside: () => ReadonlyArray<Element | null>,\n onAway: () => void,\n) => {\n React.useEffect(() => {\n if (!active) return;\n\n const handlePointerDown = (event: globalThis.PointerEvent) => {\n const target = event.target as Node | null;\n if (target && inside().some((el) => el?.contains(target))) return;\n onAway();\n };\n\n document.addEventListener('pointerdown', handlePointerDown, true);\n return () => document.removeEventListener('pointerdown', handlePointerDown, true);\n }, [active, inside, onAway]);\n};\n\n/** Whatever is still pending when the card unmounts is cancelled with it. */\nconst useClearOnUnmount = (...refs: TimerRef[]) => {\n React.useEffect(\n () => () => refs.forEach(clearPending),\n // The refs are stable objects: a mount/unmount pair, as before.\n [],\n );\n};\n\ninterface HoverCardTimingInput {\n disabled: boolean;\n touchEnabled: boolean;\n enterDelay: number;\n exitDelay: number;\n onOpen?: () => void;\n onClose?: () => void;\n}\n\n/**\n * When the card is open, and the delays that decide it.\n *\n * Three timers are in play: the enter delay before opening, the exit delay\n * before closing (so the pointer can travel from the trigger onto the card\n * without dismissing it), and the long-press timer on touch. Each cancels the\n * others' pending work, which is why they live together.\n */\nexport const useHoverCard = ({\n disabled,\n touchEnabled,\n enterDelay,\n exitDelay,\n onOpen,\n onClose,\n}: HoverCardTimingInput) => {\n const [anchorEl, setAnchorEl] = React.useState<HTMLElement | null>(null);\n const [isOpen, setIsOpen] = React.useState(false);\n const [isTouchDevice, setIsTouchDevice] = React.useState(false);\n const enterTimeoutRef = React.useRef<number | undefined>(undefined);\n const exitTimeoutRef = React.useRef<number | undefined>(undefined);\n /** The card's own box, inside the popover paper: a press here is not \"away\". */\n const contentRef = React.useRef<HTMLDivElement | null>(null);\n\n // Probed in an effect, not during render, so SSR and hydration stay intact.\n React.useEffect(() => {\n setIsTouchDevice('ontouchstart' in window);\n }, []);\n\n const openCard = React.useCallback(\n (trigger: HTMLElement) => {\n if (disabled) return;\n\n clearPending(exitTimeoutRef);\n // A second enter before the delay ran out (a click and then a hover, or\n // the pointer jittering across the edge) must REPLACE the pending open,\n // not add one: an overwritten timer can no longer be cancelled, and it\n // reopened the card after Escape had closed it.\n clearPending(enterTimeoutRef);\n\n enterTimeoutRef.current = window.setTimeout(() => {\n setAnchorEl(trigger);\n setIsOpen(true);\n onOpen?.();\n }, enterDelay);\n },\n [disabled, enterDelay, onOpen],\n );\n\n const closeCard = React.useCallback(() => {\n clearPending(enterTimeoutRef);\n clearPending(exitTimeoutRef);\n\n exitTimeoutRef.current = window.setTimeout(() => {\n setIsOpen(false);\n setAnchorEl(null);\n onClose?.();\n }, exitDelay);\n }, [exitDelay, onClose]);\n\n // Escape and the popover's own close are FINAL: nothing still pending may\n // reopen the card behind them. It opens again on the next enter.\n const handleClose = React.useCallback(() => {\n clearPending(enterTimeoutRef);\n clearPending(exitTimeoutRef);\n setIsOpen(false);\n setAnchorEl(null);\n onClose?.();\n }, [onClose]);\n\n const { triggerHandlers, cardHandlers } = useHoverHandlers({\n isTouchDevice,\n touchEnabled,\n enterDelay,\n openCard,\n closeCard,\n exitTimeoutRef,\n });\n\n const insideCard = React.useCallback(() => [anchorEl, contentRef.current], [anchorEl]);\n\n useEscapeKey(isOpen, handleClose);\n useClickAway(isOpen, insideCard, handleClose);\n useClearOnUnmount(enterTimeoutRef, exitTimeoutRef);\n\n return { anchorEl, isOpen, handleClose, triggerHandlers, cardHandlers, contentRef };\n};\n"],"mappings":"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","names":[]}
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-4XK6JQ4D.js";
4
4
  import {
5
5
  Input
6
- } from "./chunk-VURQZBFH.js";
6
+ } from "./chunk-JRJCBDGN.js";
7
7
  import {
8
8
  FormControl,
9
9
  FormLabel,
@@ -38,4 +38,4 @@ function TextField({ name, label, type = "text", placeholder }) {
38
38
  export {
39
39
  TextField
40
40
  };
41
- //# sourceMappingURL=chunk-SSS64KYB.js.map
41
+ //# sourceMappingURL=chunk-Z43N7ASP.js.map
@@ -10,7 +10,7 @@ import {
10
10
  import {
11
11
  ViewDialogs,
12
12
  ViewMutationErrorAlert
13
- } from "./chunk-A5B7B4HT.js";
13
+ } from "./chunk-INMKA5SF.js";
14
14
  import {
15
15
  useSavedViewsController
16
16
  } from "./chunk-HR7YTBR7.js";
@@ -178,4 +178,4 @@ function renderViewDialogs(ctl, fields, columns, views, testIdPrefix, onSaved) {
178
178
  export {
179
179
  DataViewsTableBase
180
180
  };
181
- //# sourceMappingURL=chunk-6MFMLQ72.js.map
181
+ //# sourceMappingURL=chunk-ZNDDILNQ.js.map
@@ -3,7 +3,7 @@ import {
3
3
  } from "../chunk-I5T5PQQW.js";
4
4
  import {
5
5
  DataViewsTableBase
6
- } from "../chunk-6MFMLQ72.js";
6
+ } from "../chunk-ZNDDILNQ.js";
7
7
  import "../chunk-3333KCAW.js";
8
8
  import {
9
9
  DataViewsGrid
@@ -68,8 +68,8 @@ import {
68
68
  cardScaleForZoom
69
69
  } from "../chunk-XZJM6EPC.js";
70
70
  import "../chunk-M4KVKQRN.js";
71
- import "../chunk-A5B7B4HT.js";
72
- import "../chunk-VJG7NMGL.js";
71
+ import "../chunk-INMKA5SF.js";
72
+ import "../chunk-IYF246UA.js";
73
73
  import "../chunk-ECIHOZPK.js";
74
74
  import "../chunk-5M4TYFR3.js";
75
75
  import "../chunk-HR7YTBR7.js";
@@ -151,7 +151,7 @@ import "../chunk-LXT3X33C.js";
151
151
  import "../chunk-DMNZD3Q7.js";
152
152
  import "../chunk-JC22FNFA.js";
153
153
  import "../chunk-QYKDY4EI.js";
154
- import "../chunk-VURQZBFH.js";
154
+ import "../chunk-JRJCBDGN.js";
155
155
  import "../chunk-SL6454K7.js";
156
156
  import "../chunk-THMT3G3L.js";
157
157
  import "../chunk-RNIU447I.js";
@@ -1,9 +1,9 @@
1
1
  import "../chunk-6OS4AKWT.js";
2
2
  import {
3
3
  HoverCard
4
- } from "../chunk-DAHYLPK7.js";
4
+ } from "../chunk-CMV6BRQ3.js";
5
5
  import "../chunk-BSYPPO2I.js";
6
- import "../chunk-SXYIXCUB.js";
6
+ import "../chunk-VNWZWQF3.js";
7
7
  import "../chunk-NRIV2CQ2.js";
8
8
  import "../chunk-ZHV7TCAC.js";
9
9
  import "../chunk-HPK33ACY.js";
@@ -1,9 +1,9 @@
1
1
  import "../chunk-6XLMTOJ3.js";
2
2
  import {
3
3
  CepField
4
- } from "../chunk-F6D6HXZU.js";
4
+ } from "../chunk-7X7ZHJHM.js";
5
5
  import "../chunk-QYKDY4EI.js";
6
- import "../chunk-VURQZBFH.js";
6
+ import "../chunk-JRJCBDGN.js";
7
7
  import "../chunk-SL6454K7.js";
8
8
  import "../chunk-THMT3G3L.js";
9
9
  import "../chunk-RNIU447I.js";
@@ -1,7 +1,7 @@
1
1
  import "../chunk-QYKDY4EI.js";
2
2
  import {
3
3
  Input
4
- } from "../chunk-VURQZBFH.js";
4
+ } from "../chunk-JRJCBDGN.js";
5
5
  import "../chunk-SL6454K7.js";
6
6
  import "../chunk-THMT3G3L.js";
7
7
  import "../chunk-RNIU447I.js";
@@ -1,8 +1,8 @@
1
1
  import "../chunk-WMY276HD.js";
2
2
  import {
3
3
  RichTextEditor
4
- } from "../chunk-7EM4THID.js";
5
- import "../chunk-774B27QA.js";
4
+ } from "../chunk-RTEY5COP.js";
5
+ import "../chunk-EUACQR6N.js";
6
6
  import "../chunk-UZOG6HNN.js";
7
7
  import "../chunk-33LAPBYB.js";
8
8
  import "../chunk-WVZBYTIK.js";
@@ -9,15 +9,15 @@ import {
9
9
  } from "../chunk-CPAJYJUD.js";
10
10
  import {
11
11
  Fields_exports
12
- } from "../chunk-YS67XMOP.js";
12
+ } from "../chunk-I5ZVCY2N.js";
13
13
  import "../chunk-FNG33MKA.js";
14
14
  import {
15
15
  TextField
16
- } from "../chunk-SSS64KYB.js";
16
+ } from "../chunk-Z43N7ASP.js";
17
17
  import {
18
18
  SelectField
19
19
  } from "../chunk-PWZHCBG3.js";
20
- import "../chunk-2XX45PC5.js";
20
+ import "../chunk-TGXCG62P.js";
21
21
  import {
22
22
  CategoryField
23
23
  } from "../chunk-T6EKPFKZ.js";
@@ -33,7 +33,7 @@ import "../chunk-W5TI46QN.js";
33
33
  import "../chunk-JD5BVIM7.js";
34
34
  import "../chunk-VQYR64QB.js";
35
35
  import "../chunk-6XLMTOJ3.js";
36
- import "../chunk-F6D6HXZU.js";
36
+ import "../chunk-7X7ZHJHM.js";
37
37
  import "../chunk-N5ZWC6CB.js";
38
38
  import "../chunk-BKASAYQL.js";
39
39
  import "../chunk-2JNHWKQK.js";
@@ -52,7 +52,7 @@ import "../chunk-LXT3X33C.js";
52
52
  import "../chunk-DMNZD3Q7.js";
53
53
  import "../chunk-JC22FNFA.js";
54
54
  import "../chunk-QYKDY4EI.js";
55
- import "../chunk-VURQZBFH.js";
55
+ import "../chunk-JRJCBDGN.js";
56
56
  import "../chunk-SL6454K7.js";
57
57
  import "../chunk-THMT3G3L.js";
58
58
  import "../chunk-RNIU447I.js";
@@ -1 +1 @@
1
- {"version":3,"file":"HoverCard.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/HoverCard/HoverCard.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,MAAM,OAAO,CAAC;AAS1B,OAAO,KAAK,EAA0C,cAAc,EAAE,MAAM,mBAAmB,CAAC;AA6DhG,eAAO,MAAM,SAAS,oGA8EpB,CAAC"}
1
+ {"version":3,"file":"HoverCard.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/HoverCard/HoverCard.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,MAAM,OAAO,CAAC;AAS1B,OAAO,KAAK,EAA0C,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAsEhG,eAAO,MAAM,SAAS,oGA8EpB,CAAC"}
@@ -30,6 +30,7 @@ export declare const useHoverCard: ({ disabled, touchEnabled, enterDelay, exitDe
30
30
  onMouseEnter: () => void;
31
31
  onMouseLeave: () => void;
32
32
  };
33
+ contentRef: React.RefObject<HTMLDivElement | null>;
33
34
  };
34
35
  export {};
35
36
  //# sourceMappingURL=HoverCard.hooks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"HoverCard.hooks.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/HoverCard/HoverCard.hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAuF1B,UAAU,oBAAoB;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,GAAI,qEAO1B,oBAAoB;;;;;8BAnEG,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC;;8BAQ7B,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC;;;;;;4BA7CpC,IAAI;;CA4KtB,CAAC"}
1
+ {"version":3,"file":"HoverCard.hooks.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/HoverCard/HoverCard.hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AA2I1B,UAAU,oBAAoB;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,GAAI,qEAO1B,oBAAoB;;;;;8BA3GG,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC;;8BAQ7B,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC;;;;;;4BA7CpC,IAAI;;;CAuNtB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"Input.d.ts","sourceRoot":"","sources":["../../../../../src/components/form/Input/Input.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,OAAO,CAAC;AAgB1B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAwEhD,eAAO,MAAM,KAAK,qFAgEjB,CAAC"}
1
+ {"version":3,"file":"Input.d.ts","sourceRoot":"","sources":["../../../../../src/components/form/Input/Input.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,OAAO,CAAC;AAgB1B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAyGhD,eAAO,MAAM,KAAK,qFAsEjB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"RichTextEditor.hooks.d.ts","sourceRoot":"","sources":["../../../../../src/components/form/RichTextEditor/RichTextEditor.hooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAwE/B,eAAO,MAAM,iBAAiB,GAAI,sEAQ/B;IACD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;;;;;;;8BAWgB,MAAM,gBAAgB,MAAM;CAyC5C,CAAC"}
1
+ {"version":3,"file":"RichTextEditor.hooks.d.ts","sourceRoot":"","sources":["../../../../../src/components/form/RichTextEditor/RichTextEditor.hooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAyF/B,eAAO,MAAM,iBAAiB,GAAI,sEAQ/B;IACD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;;;;;;;8BAWgB,MAAM,gBAAgB,MAAM;CAyC5C,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@12-apps/ui",
3
- "version": "6.39.10",
3
+ "version": "6.39.12",
4
4
  "license": "MIT",
5
5
  "packageManager": "pnpm@10.34.5",
6
6
  "type": "module",
@@ -1,12 +1,24 @@
1
1
  import React from 'react';
2
2
 
3
+ type TimerRef = React.MutableRefObject<number | undefined>;
4
+
5
+ /**
6
+ * Cancel the timer a ref holds, and forget it. Every place that schedules
7
+ * or dismisses goes through this, so no timer outlives the ref that could
8
+ * cancel it (FUT-2619: an overwritten enter timer reopened a dismissed card).
9
+ */
10
+ const clearPending = (ref: TimerRef) => {
11
+ if (ref.current !== undefined) window.clearTimeout(ref.current);
12
+ ref.current = undefined;
13
+ };
14
+
3
15
  interface HandlerInput {
4
16
  isTouchDevice: boolean;
5
17
  touchEnabled: boolean;
6
18
  enterDelay: number;
7
- openCard: (event: React.MouseEvent<HTMLElement> | React.TouchEvent<HTMLElement>) => void;
19
+ openCard: (trigger: HTMLElement) => void;
8
20
  closeCard: () => void;
9
- exitTimeoutRef: React.MutableRefObject<number | undefined>;
21
+ exitTimeoutRef: TimerRef;
10
22
  }
11
23
 
12
24
  /**
@@ -44,7 +56,7 @@ const useHoverHandlers = ({
44
56
  const triggerHandlers = {
45
57
  onMouseEnter: (event: React.MouseEvent<HTMLElement>) => {
46
58
  if (hoverIgnored) return;
47
- openCard(event);
59
+ openCard(event.currentTarget);
48
60
  },
49
61
  onMouseLeave: () => {
50
62
  if (hoverIgnored) return;
@@ -53,7 +65,10 @@ const useHoverHandlers = ({
53
65
  onTouchStart: (event: React.TouchEvent<HTMLElement>) => {
54
66
  if (!touchEnabled) return;
55
67
  if (touchTimeout) window.clearTimeout(touchTimeout);
56
- setTouchTimeout(window.setTimeout(() => openCard(event), enterDelay));
68
+ // Read NOW: React nulls `currentTarget` once the handler returns, so
69
+ // the long-press timer used to open the card with no anchor at all.
70
+ const trigger = event.currentTarget;
71
+ setTouchTimeout(window.setTimeout(() => openCard(trigger), enterDelay));
57
72
  },
58
73
  // End and cancel do the same thing: the press did not become a long press.
59
74
  onTouchEnd: cancelTouch,
@@ -62,9 +77,7 @@ const useHoverHandlers = ({
62
77
 
63
78
  /** Moving onto the card keeps it open; leaving it starts the exit delay. */
64
79
  const cardHandlers = {
65
- onMouseEnter: () => {
66
- if (exitTimeoutRef.current) window.clearTimeout(exitTimeoutRef.current);
67
- },
80
+ onMouseEnter: () => clearPending(exitTimeoutRef),
68
81
  onMouseLeave: closeCard,
69
82
  };
70
83
 
@@ -85,6 +98,45 @@ const useEscapeKey = (active: boolean, onEscape: () => void) => {
85
98
  }, [active, onEscape]);
86
99
  };
87
100
 
101
+ /**
102
+ * A press anywhere outside the trigger and the card dismisses it.
103
+ *
104
+ * Touch has no mouse-leave, and MUI's own click-away was the popover's
105
+ * backdrop, which `pointer-events: none` on the popover root (so the page
106
+ * under an open card stays usable) takes out of play. So this listens on the
107
+ * document itself, in the CAPTURE phase, where a `stopPropagation` further in
108
+ * cannot swallow it. It only observes: no `preventDefault`, so the tap or
109
+ * click still reaches whatever it landed on. Attached while open, removed on
110
+ * close and on unmount.
111
+ */
112
+ const useClickAway = (
113
+ active: boolean,
114
+ inside: () => ReadonlyArray<Element | null>,
115
+ onAway: () => void,
116
+ ) => {
117
+ React.useEffect(() => {
118
+ if (!active) return;
119
+
120
+ const handlePointerDown = (event: globalThis.PointerEvent) => {
121
+ const target = event.target as Node | null;
122
+ if (target && inside().some((el) => el?.contains(target))) return;
123
+ onAway();
124
+ };
125
+
126
+ document.addEventListener('pointerdown', handlePointerDown, true);
127
+ return () => document.removeEventListener('pointerdown', handlePointerDown, true);
128
+ }, [active, inside, onAway]);
129
+ };
130
+
131
+ /** Whatever is still pending when the card unmounts is cancelled with it. */
132
+ const useClearOnUnmount = (...refs: TimerRef[]) => {
133
+ React.useEffect(
134
+ () => () => refs.forEach(clearPending),
135
+ // The refs are stable objects: a mount/unmount pair, as before.
136
+ [],
137
+ );
138
+ };
139
+
88
140
  interface HoverCardTimingInput {
89
141
  disabled: boolean;
90
142
  touchEnabled: boolean;
@@ -115,6 +167,8 @@ export const useHoverCard = ({
115
167
  const [isTouchDevice, setIsTouchDevice] = React.useState(false);
116
168
  const enterTimeoutRef = React.useRef<number | undefined>(undefined);
117
169
  const exitTimeoutRef = React.useRef<number | undefined>(undefined);
170
+ /** The card's own box, inside the popover paper: a press here is not "away". */
171
+ const contentRef = React.useRef<HTMLDivElement | null>(null);
118
172
 
119
173
  // Probed in an effect, not during render, so SSR and hydration stay intact.
120
174
  React.useEffect(() => {
@@ -122,16 +176,18 @@ export const useHoverCard = ({
122
176
  }, []);
123
177
 
124
178
  const openCard = React.useCallback(
125
- (event: React.MouseEvent<HTMLElement> | React.TouchEvent<HTMLElement>) => {
179
+ (trigger: HTMLElement) => {
126
180
  if (disabled) return;
127
181
 
128
- if (exitTimeoutRef.current) {
129
- window.clearTimeout(exitTimeoutRef.current);
130
- }
182
+ clearPending(exitTimeoutRef);
183
+ // A second enter before the delay ran out (a click and then a hover, or
184
+ // the pointer jittering across the edge) must REPLACE the pending open,
185
+ // not add one: an overwritten timer can no longer be cancelled, and it
186
+ // reopened the card after Escape had closed it.
187
+ clearPending(enterTimeoutRef);
131
188
 
132
- const target = event.currentTarget;
133
189
  enterTimeoutRef.current = window.setTimeout(() => {
134
- setAnchorEl(target);
190
+ setAnchorEl(trigger);
135
191
  setIsOpen(true);
136
192
  onOpen?.();
137
193
  }, enterDelay);
@@ -140,9 +196,8 @@ export const useHoverCard = ({
140
196
  );
141
197
 
142
198
  const closeCard = React.useCallback(() => {
143
- if (enterTimeoutRef.current) {
144
- window.clearTimeout(enterTimeoutRef.current);
145
- }
199
+ clearPending(enterTimeoutRef);
200
+ clearPending(exitTimeoutRef);
146
201
 
147
202
  exitTimeoutRef.current = window.setTimeout(() => {
148
203
  setIsOpen(false);
@@ -151,7 +206,11 @@ export const useHoverCard = ({
151
206
  }, exitDelay);
152
207
  }, [exitDelay, onClose]);
153
208
 
209
+ // Escape and the popover's own close are FINAL: nothing still pending may
210
+ // reopen the card behind them. It opens again on the next enter.
154
211
  const handleClose = React.useCallback(() => {
212
+ clearPending(enterTimeoutRef);
213
+ clearPending(exitTimeoutRef);
155
214
  setIsOpen(false);
156
215
  setAnchorEl(null);
157
216
  onClose?.();
@@ -166,15 +225,11 @@ export const useHoverCard = ({
166
225
  exitTimeoutRef,
167
226
  });
168
227
 
169
- useEscapeKey(isOpen, handleClose);
228
+ const insideCard = React.useCallback(() => [anchorEl, contentRef.current], [anchorEl]);
170
229
 
171
- React.useEffect(
172
- () => () => {
173
- if (enterTimeoutRef.current) window.clearTimeout(enterTimeoutRef.current);
174
- if (exitTimeoutRef.current) window.clearTimeout(exitTimeoutRef.current);
175
- },
176
- [],
177
- );
230
+ useEscapeKey(isOpen, handleClose);
231
+ useClickAway(isOpen, insideCard, handleClose);
232
+ useClearOnUnmount(enterTimeoutRef, exitTimeoutRef);
178
233
 
179
- return { anchorEl, isOpen, handleClose, triggerHandlers, cardHandlers };
234
+ return { anchorEl, isOpen, handleClose, triggerHandlers, cardHandlers, contentRef };
180
235
  };
@@ -107,6 +107,7 @@ The HoverCard component displays additional information in a card-style popup wh
107
107
  ## Accessibility Notes
108
108
 
109
109
  - The component supports keyboard navigation (Escape key to close)
110
+ - The card is dismissed by moving the pointer off the trigger and the card (after `exitDelay`), by Escape, or by a tap or click anywhere outside them. The outside press still reaches what it landed on: the page under an open card stays usable
110
111
  - Proper ARIA attributes are applied to the popover
111
112
  - Focus management is handled automatically
112
113
  - Touch support for mobile devices can be enabled/disabled
@@ -26,6 +26,15 @@ const StyledCard = styled(Card, {
26
26
  const StyledPopover = styled(Popover, {
27
27
  shouldForwardProp: (prop) => prop !== 'customAnimation',
28
28
  })<{ customAnimation?: HoverCardAnimation }>(({ customAnimation }) => ({
29
+ // Only the card takes the pointer (its paper sets `pointer-events: auto`).
30
+ // MUI's popover root and its invisible backdrop cover the whole viewport, so
31
+ // while a card was open the pointer was "on the card" wherever it went: the
32
+ // backdrop's mouseover reached the card's onMouseEnter and cancelled the
33
+ // close the trigger's mouseleave had just scheduled. A card left by moving
34
+ // the mouse away stayed open, and the page's next click hit the backdrop
35
+ // instead of its target (FUT-2619). The backdrop was also the only
36
+ // click-away; `useClickAway` in the hooks replaces it.
37
+ pointerEvents: 'none',
29
38
  // The popover only positions; StyledCard draws the surface.
30
39
  '& .MuiPopover-paper': {
31
40
  backgroundColor: 'transparent',
@@ -81,7 +90,7 @@ export const HoverCard = React.forwardRef<HTMLDivElement, HoverCardProps>((props
81
90
  ...rest
82
91
  } = withDefaults(props, DEFAULTS) as ResolvedProps;
83
92
 
84
- const { anchorEl, isOpen, handleClose, triggerHandlers, cardHandlers } = useHoverCard({
93
+ const { anchorEl, isOpen, handleClose, triggerHandlers, cardHandlers, contentRef } = useHoverCard({
85
94
  disabled,
86
95
  touchEnabled,
87
96
  enterDelay,
@@ -124,7 +133,7 @@ export const HoverCard = React.forwardRef<HTMLDivElement, HoverCardProps>((props
124
133
  slotProps={{ paper: { style: { pointerEvents: 'auto' } } }}
125
134
  {...rest}
126
135
  >
127
- <Box sx={{ position: 'relative' }}>
136
+ <Box ref={contentRef} sx={{ position: 'relative' }}>
128
137
  {showArrow && <ArrowContainer placement={placement} offset={offset} />}
129
138
  <StyledCard
130
139
  customVariant={variant}
@@ -89,6 +89,39 @@ const interactionProps = (
89
89
  onClick: loading ? undefined : onClick,
90
90
  });
91
91
 
92
+ /**
93
+ * The ARIA that describes the field has to sit on the `<input>`: the text
94
+ * field puts every attribute it does not recognise on its root `FormControl`,
95
+ * a `<div>` with no role, where a screen reader never reads it.
96
+ *
97
+ * `aria-label` has ridden `inputProps` since FUT-755. `aria-describedby` and
98
+ * `aria-busy` fell through to the div until FUT-2619, so CepField's input
99
+ * pointed at nothing while its lookup status lived in a live region beside it.
100
+ *
101
+ * `inputProps` are spread onto the `<input>` AFTER the `aria-describedby` MUI
102
+ * writes for helper text, so a caller's ids REPLACE the helper text's: MUI's
103
+ * `TextField` on its own keeps only one of the two. This joins both, helper
104
+ * text first. The helper id is MUI's `<id>-helper-text` (the parity test pins
105
+ * that spelling), which is why the field's id is fixed here rather than left
106
+ * to the text field.
107
+ */
108
+ function inputAriaOf(a: {
109
+ id: string;
110
+ helperText: React.ReactNode;
111
+ label?: string;
112
+ describedBy?: string;
113
+ busy?: InputProps['aria-busy'];
114
+ }): Record<string, unknown> {
115
+ const describedBy = [a.helperText ? `${a.id}-helper-text` : undefined, a.describedBy]
116
+ .filter(Boolean)
117
+ .join(' ');
118
+ return {
119
+ ...(a.label === undefined ? {} : { 'aria-label': a.label }),
120
+ ...(a.describedBy === undefined ? {} : { 'aria-describedby': describedBy }),
121
+ ...(a.busy === undefined ? {} : { 'aria-busy': a.busy }),
122
+ };
123
+ }
124
+
92
125
  export const Input = React.forwardRef<HTMLInputElement, InputProps>(
93
126
  (
94
127
  {
@@ -108,11 +141,15 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
108
141
  onFocus,
109
142
  onBlur,
110
143
  'aria-label': ariaLabel,
144
+ 'aria-describedby': ariaDescribedBy,
145
+ 'aria-busy': ariaBusy,
111
146
  ...rest
112
147
  },
113
148
  ref,
114
149
  ) => {
115
150
  const { testId: dataTestId, rest: props } = splitTestId(rest);
151
+ const generatedId = React.useId();
152
+ const id = props.id ?? generatedId;
116
153
  return (
117
154
  <StyledTextField
118
155
  ref={ref}
@@ -137,10 +174,11 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
137
174
  * no accessible name, and a source grep saying "this input is labelled"
138
175
  * disagreed with the DOM. The reports search box and the block-title
139
176
  * inputs were both named in source and anonymous to a screen reader.
177
+ * `aria-describedby` and `aria-busy` are the same case: `inputAriaOf`.
140
178
  */
141
179
  inputProps={{
142
180
  'data-testid': dataTestId,
143
- ...(ariaLabel === undefined ? {} : { 'aria-label': ariaLabel }),
181
+ ...inputAriaOf({ id, helperText, label: ariaLabel, describedBy: ariaDescribedBy, busy: ariaBusy }),
144
182
  }}
145
183
  InputProps={{
146
184
  startAdornment: startAdornment && (
@@ -150,6 +188,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
150
188
  }}
151
189
  {...SIZE_MAP[size]}
152
190
  {...props}
191
+ id={id}
153
192
  />
154
193
  );
155
194
  },