@arkyn/components 3.0.10 → 3.0.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 (42) hide show
  1. package/README.md +1 -1
  2. package/dist/components/__test__/calendar.test-d.d.ts +2 -0
  3. package/dist/components/__test__/calendar.test-d.d.ts.map +1 -0
  4. package/dist/components/__test__/datePicker.test-d.d.ts +2 -0
  5. package/dist/components/__test__/datePicker.test-d.d.ts.map +1 -0
  6. package/dist/components/audioUpload/hasFileContent/index.d.ts +1 -0
  7. package/dist/components/audioUpload/hasFileContent/index.d.ts.map +1 -1
  8. package/dist/components/audioUpload/index.d.ts +9 -0
  9. package/dist/components/audioUpload/index.d.ts.map +1 -1
  10. package/dist/components/checkbox/index.d.ts.map +1 -1
  11. package/dist/components/radio/radioBox/index.d.ts.map +1 -1
  12. package/dist/components/slider/index.d.ts +7 -29
  13. package/dist/components/slider/index.d.ts.map +1 -1
  14. package/dist/components/switch/index.d.ts.map +1 -1
  15. package/dist/components/tooltip/index.d.ts +8 -2
  16. package/dist/components/tooltip/index.d.ts.map +1 -1
  17. package/dist/hooks/useAutomation.d.ts.map +1 -1
  18. package/dist/index.js +814 -732
  19. package/dist/index.js.map +1 -1
  20. package/dist/modules/components/audioUpload/hasFileContent/index.js +5 -5
  21. package/dist/modules/components/audioUpload/hasFileContent/index.js.map +1 -1
  22. package/dist/modules/components/audioUpload/index.js +30 -29
  23. package/dist/modules/components/audioUpload/index.js.map +1 -1
  24. package/dist/modules/components/checkbox/index.js +2 -0
  25. package/dist/modules/components/checkbox/index.js.map +1 -1
  26. package/dist/modules/components/radio/radioBox/index.js +2 -0
  27. package/dist/modules/components/radio/radioBox/index.js.map +1 -1
  28. package/dist/modules/components/radio/radioGroup/index.js +1 -0
  29. package/dist/modules/components/radio/radioGroup/index.js.map +1 -1
  30. package/dist/modules/components/slider/index.js +76 -27
  31. package/dist/modules/components/slider/index.js.map +1 -1
  32. package/dist/modules/components/switch/index.js +2 -0
  33. package/dist/modules/components/switch/index.js.map +1 -1
  34. package/dist/modules/components/tooltip/index.js +20 -19
  35. package/dist/modules/components/tooltip/index.js.map +1 -1
  36. package/dist/modules/hooks/useAutomation.js +29 -30
  37. package/dist/modules/hooks/useAutomation.js.map +1 -1
  38. package/dist/modules/utils/sanitizeTooltipHtml.js +29 -0
  39. package/dist/modules/utils/sanitizeTooltipHtml.js.map +1 -0
  40. package/dist/utils/sanitizeTooltipHtml.d.ts +16 -0
  41. package/dist/utils/sanitizeTooltipHtml.d.ts.map +1 -0
  42. package/package.json +3 -2
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../../src/components/tooltip/index.tsx"],"sourcesContent":["import {\n\ttype HTMLAttributes,\n\ttype ReactNode,\n\tuseEffect,\n\tuseId,\n\tuseRef,\n\tuseState,\n} from \"react\";\nimport \"./styles.css\";\n\ntype TooltipProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\"> & {\n\t/** Text rendered inside the tooltip bubble. Supports inline HTML. Required. */\n\ttext: string;\n\t/** Element that triggers the tooltip on hover. Required. */\n\tchildren: ReactNode;\n\t/**\n\t * Preferred position of the tooltip relative to the trigger element.\n\t * Automatically flips to the opposite side if the tooltip would overflow the viewport.\n\t * @default \"top\"\n\t */\n\torientation?: \"top\" | \"right\" | \"bottom\" | \"left\";\n\t/**\n\t * Tooltip size.\n\t * @default \"lg\"\n\t */\n\tsize?: \"md\" | \"lg\";\n};\n\n/**\n * Tooltip, shows a contextual text bubble on hover with smart viewport-aware positioning.\n *\n * The tooltip automatically flips to the opposite side when it would overflow the viewport.\n *\n * @param props.text - Text (or HTML) to display in the tooltip. Required.\n * @param props.children - Trigger element. Required.\n * @param props.orientation - Preferred position relative to the trigger. Default: \"top\"\n * @param props.size - Tooltip size. Default: \"lg\"\n *\n * **...Other valid HTML properties for `<div>`**\n *\n * @returns Tooltip JSX element.\n *\n * @example\n * ```tsx\n * <Tooltip text=\"Save changes before leaving\">\n * <IconButton icon={SaveIcon} aria-label=\"Save\" />\n * </Tooltip>\n *\n * // Toolbar with bottom-oriented tooltips\n * <Tooltip text=\"Add item\" orientation=\"bottom\">\n * <IconButton icon={Plus} aria-label=\"Add\" />\n * </Tooltip>\n *\n * <Tooltip text=\"Delete\" orientation=\"bottom\" size=\"md\">\n * <IconButton icon={Trash2} aria-label=\"Delete\" scheme=\"danger\" />\n * </Tooltip>\n * ```\n */\n\nfunction Tooltip(props: TooltipProps) {\n\tconst {\n\t\ttext,\n\t\tsize = \"lg\",\n\t\tchildren,\n\t\torientation = \"top\",\n\t\tclassName: baseClassName = \"\",\n\t\t...rest\n\t} = props;\n\n\tconst tooltipId = useId();\n\tconst tooltipRef = useRef<HTMLDivElement>(null);\n\tconst [adjustedOrientation, setAdjustedOrientation] = useState(orientation);\n\n\tuseEffect(() => {\n\t\tconst checkTooltipPosition = () => {\n\t\t\tif (!tooltipRef.current) return;\n\t\t\tconst tooltipText = document.getElementById(tooltipId) as HTMLElement;\n\t\t\tif (!tooltipText) return;\n\n\t\t\tsetAdjustedOrientation(orientation);\n\n\t\t\trequestAnimationFrame(() => {\n\t\t\t\tconst rect = tooltipText.getBoundingClientRect();\n\t\t\t\tconst viewportWidth = window.innerWidth;\n\t\t\t\tconst viewportHeight = window.innerHeight;\n\n\t\t\t\tlet newOrientation = orientation;\n\n\t\t\t\tif (orientation === \"left\" && rect.left < 0) {\n\t\t\t\t\tnewOrientation = \"right\";\n\t\t\t\t} else if (orientation === \"right\" && rect.right > viewportWidth) {\n\t\t\t\t\tnewOrientation = \"left\";\n\t\t\t\t} else if (orientation === \"top\" && rect.top < 0) {\n\t\t\t\t\tnewOrientation = \"bottom\";\n\t\t\t\t} else if (orientation === \"bottom\" && rect.bottom > viewportHeight) {\n\t\t\t\t\tnewOrientation = \"top\";\n\t\t\t\t}\n\n\t\t\t\tif (newOrientation === \"right\" && rect.right > viewportWidth) {\n\t\t\t\t\tnewOrientation = \"left\";\n\t\t\t\t} else if (newOrientation === \"left\" && rect.left < 0) {\n\t\t\t\t\tnewOrientation = \"right\";\n\t\t\t\t} else if (\n\t\t\t\t\tnewOrientation === \"bottom\" &&\n\t\t\t\t\trect.bottom > viewportHeight\n\t\t\t\t) {\n\t\t\t\t\tnewOrientation = \"top\";\n\t\t\t\t} else if (newOrientation === \"top\" && rect.top < 0) {\n\t\t\t\t\tnewOrientation = \"bottom\";\n\t\t\t\t}\n\n\t\t\t\tsetAdjustedOrientation(newOrientation);\n\t\t\t});\n\t\t};\n\n\t\tconst tooltip = tooltipRef.current;\n\t\tif (!tooltip) return;\n\n\t\tconst handleMouseEnter = () => {\n\t\t\tsetTimeout(checkTooltipPosition, 1);\n\t\t};\n\n\t\ttooltip.addEventListener(\"mouseenter\", handleMouseEnter);\n\t\twindow.addEventListener(\"resize\", checkTooltipPosition);\n\n\t\treturn () => {\n\t\t\ttooltip.removeEventListener(\"mouseenter\", handleMouseEnter);\n\t\t\twindow.removeEventListener(\"resize\", checkTooltipPosition);\n\t\t};\n\t}, [orientation, tooltipId]);\n\n\tconst className = `arkynTooltip ${size} ${adjustedOrientation} ${baseClassName}`;\n\n\treturn (\n\t\t<div className={className.trim()} {...rest} ref={tooltipRef}>\n\t\t\t{children}\n\n\t\t\t<div\n\t\t\t\tclassName=\"arkynTooltipText\"\n\t\t\t\tid={tooltipId}\n\t\t\t\tdangerouslySetInnerHTML={{ __html: text }}\n\t\t\t/>\n\t\t</div>\n\t);\n}\n\nexport { Tooltip };\n"],"mappings":";;;;AA2DA,SAAS,EAAQ,GAAqB;CACrC,IAAM,EACL,SACA,UAAO,MACP,aACA,iBAAc,OACd,WAAW,IAAgB,IAC3B,GAAG,MACA,GAEE,IAAY,EAAM,GAClB,IAAa,EAAuB,IAAI,GACxC,CAAC,GAAqB,KAA0B,EAAS,CAAW;CA8D1E,OA5DA,QAAgB;EACf,IAAM,UAA6B;GAClC,IAAI,CAAC,EAAW,SAAS;GACzB,IAAM,IAAc,SAAS,eAAe,CAAS;GAChD,MAEL,EAAuB,CAAW,GAElC,4BAA4B;IAC3B,IAAM,IAAO,EAAY,sBAAsB,GACzC,IAAgB,OAAO,YACvB,IAAiB,OAAO,aAE1B,IAAiB;IAyBrB,AAvBI,MAAgB,UAAU,EAAK,OAAO,IACzC,IAAiB,UACP,MAAgB,WAAW,EAAK,QAAQ,IAClD,IAAiB,SACP,MAAgB,SAAS,EAAK,MAAM,IAC9C,IAAiB,WACP,MAAgB,YAAY,EAAK,SAAS,MACpD,IAAiB,QAGd,MAAmB,WAAW,EAAK,QAAQ,IAC9C,IAAiB,SACP,MAAmB,UAAU,EAAK,OAAO,IACnD,IAAiB,UAEjB,MAAmB,YACnB,EAAK,SAAS,IAEd,IAAiB,QACP,MAAmB,SAAS,EAAK,MAAM,MACjD,IAAiB,WAGlB,EAAuB,CAAc;GACtC,CAAC;EACF,GAEM,IAAU,EAAW;EAC3B,IAAI,CAAC,GAAS;EAEd,IAAM,UAAyB;GAC9B,WAAW,GAAsB,CAAC;EACnC;EAKA,OAHA,EAAQ,iBAAiB,cAAc,CAAgB,GACvD,OAAO,iBAAiB,UAAU,CAAoB,SAEzC;GAEZ,AADA,EAAQ,oBAAoB,cAAc,CAAgB,GAC1D,OAAO,oBAAoB,UAAU,CAAoB;EAC1D;CACD,GAAG,CAAC,GAAa,CAAS,CAAC,GAK1B,kBAAC,OAAD;EAAK,WAAW,gBAHiB,EAAK,GAAG,EAAoB,GAAG,IAGtC,KAAK;EAAG,GAAI;EAAM,KAAK;YAAjD,CACE,GAED,kBAAC,OAAD;GACC,WAAU;GACV,IAAI;GACJ,yBAAyB,EAAE,QAAQ,EAAK;EACxC,CAAA,CACG;;AAEP"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../../src/components/tooltip/index.tsx"],"sourcesContent":["import {\n\ttype HTMLAttributes,\n\ttype ReactNode,\n\tuseEffect,\n\tuseId,\n\tuseRef,\n\tuseState,\n} from \"react\";\nimport { sanitizeTooltipHtml } from \"../../utils/sanitizeTooltipHtml\";\nimport \"./styles.css\";\n\ntype TooltipProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\"> & {\n\t/**\n\t * Text rendered inside the tooltip bubble. Supports a small set of\n\t * inline formatting tags (`b`, `strong`, `i`, `em`, `u`, `br`, `span`,\n\t * `small`); anything else (including `<script>`, event handler\n\t * attributes and all other attributes) is stripped before rendering.\n\t * Required.\n\t */\n\ttext: string;\n\t/** Element that triggers the tooltip on hover. Required. */\n\tchildren: ReactNode;\n\t/**\n\t * Preferred position of the tooltip relative to the trigger element.\n\t * Automatically flips to the opposite side if the tooltip would overflow the viewport.\n\t * @default \"top\"\n\t */\n\torientation?: \"top\" | \"right\" | \"bottom\" | \"left\";\n\t/**\n\t * Tooltip size.\n\t * @default \"lg\"\n\t */\n\tsize?: \"md\" | \"lg\";\n};\n\n/**\n * Tooltip, shows a contextual text bubble on hover with smart viewport-aware positioning.\n *\n * The tooltip automatically flips to the opposite side when it would overflow the viewport.\n *\n * @param props.text - Text (or a small subset of inline HTML — see prop docs) to display in the tooltip. Sanitized before render. Required.\n * @param props.children - Trigger element. Required.\n * @param props.orientation - Preferred position relative to the trigger. Default: \"top\"\n * @param props.size - Tooltip size. Default: \"lg\"\n *\n * **...Other valid HTML properties for `<div>`**\n *\n * @returns Tooltip JSX element.\n *\n * @example\n * ```tsx\n * <Tooltip text=\"Save changes before leaving\">\n * <IconButton icon={SaveIcon} aria-label=\"Save\" />\n * </Tooltip>\n *\n * // Toolbar with bottom-oriented tooltips\n * <Tooltip text=\"Add item\" orientation=\"bottom\">\n * <IconButton icon={Plus} aria-label=\"Add\" />\n * </Tooltip>\n *\n * <Tooltip text=\"Delete\" orientation=\"bottom\" size=\"md\">\n * <IconButton icon={Trash2} aria-label=\"Delete\" scheme=\"danger\" />\n * </Tooltip>\n * ```\n */\n\nfunction Tooltip(props: TooltipProps) {\n\tconst {\n\t\ttext,\n\t\tsize = \"lg\",\n\t\tchildren,\n\t\torientation = \"top\",\n\t\tclassName: baseClassName = \"\",\n\t\t...rest\n\t} = props;\n\n\tconst tooltipId = useId();\n\tconst tooltipRef = useRef<HTMLDivElement>(null);\n\tconst [adjustedOrientation, setAdjustedOrientation] = useState(orientation);\n\n\tuseEffect(() => {\n\t\tconst checkTooltipPosition = () => {\n\t\t\tif (!tooltipRef.current) return;\n\t\t\tconst tooltipText = document.getElementById(tooltipId) as HTMLElement;\n\t\t\tif (!tooltipText) return;\n\n\t\t\tsetAdjustedOrientation(orientation);\n\n\t\t\trequestAnimationFrame(() => {\n\t\t\t\tconst rect = tooltipText.getBoundingClientRect();\n\t\t\t\tconst viewportWidth = window.innerWidth;\n\t\t\t\tconst viewportHeight = window.innerHeight;\n\n\t\t\t\tlet newOrientation = orientation;\n\n\t\t\t\tif (orientation === \"left\" && rect.left < 0) {\n\t\t\t\t\tnewOrientation = \"right\";\n\t\t\t\t} else if (orientation === \"right\" && rect.right > viewportWidth) {\n\t\t\t\t\tnewOrientation = \"left\";\n\t\t\t\t} else if (orientation === \"top\" && rect.top < 0) {\n\t\t\t\t\tnewOrientation = \"bottom\";\n\t\t\t\t} else if (orientation === \"bottom\" && rect.bottom > viewportHeight) {\n\t\t\t\t\tnewOrientation = \"top\";\n\t\t\t\t}\n\n\t\t\t\tif (newOrientation === \"right\" && rect.right > viewportWidth) {\n\t\t\t\t\tnewOrientation = \"left\";\n\t\t\t\t} else if (newOrientation === \"left\" && rect.left < 0) {\n\t\t\t\t\tnewOrientation = \"right\";\n\t\t\t\t} else if (\n\t\t\t\t\tnewOrientation === \"bottom\" &&\n\t\t\t\t\trect.bottom > viewportHeight\n\t\t\t\t) {\n\t\t\t\t\tnewOrientation = \"top\";\n\t\t\t\t} else if (newOrientation === \"top\" && rect.top < 0) {\n\t\t\t\t\tnewOrientation = \"bottom\";\n\t\t\t\t}\n\n\t\t\t\tsetAdjustedOrientation(newOrientation);\n\t\t\t});\n\t\t};\n\n\t\tconst tooltip = tooltipRef.current;\n\t\tif (!tooltip) return;\n\n\t\tconst handleMouseEnter = () => {\n\t\t\tsetTimeout(checkTooltipPosition, 1);\n\t\t};\n\n\t\ttooltip.addEventListener(\"mouseenter\", handleMouseEnter);\n\t\twindow.addEventListener(\"resize\", checkTooltipPosition);\n\n\t\treturn () => {\n\t\t\ttooltip.removeEventListener(\"mouseenter\", handleMouseEnter);\n\t\t\twindow.removeEventListener(\"resize\", checkTooltipPosition);\n\t\t};\n\t}, [orientation, tooltipId]);\n\n\tconst className = `arkynTooltip ${size} ${adjustedOrientation} ${baseClassName}`;\n\n\treturn (\n\t\t<div className={className.trim()} {...rest} ref={tooltipRef}>\n\t\t\t{children}\n\n\t\t\t<div\n\t\t\t\tclassName=\"arkynTooltipText\"\n\t\t\t\tid={tooltipId}\n\t\t\t\tdangerouslySetInnerHTML={{ __html: sanitizeTooltipHtml(text) }}\n\t\t\t/>\n\t\t</div>\n\t);\n}\n\nexport { Tooltip };\n"],"mappings":";;;;;AAkEA,SAAS,EAAQ,GAAqB;CACrC,IAAM,EACL,SACA,UAAO,MACP,aACA,iBAAc,OACd,WAAW,IAAgB,IAC3B,GAAG,MACA,GAEE,IAAY,EAAM,GAClB,IAAa,EAAuB,IAAI,GACxC,CAAC,GAAqB,KAA0B,EAAS,CAAW;CA8D1E,OA5DA,QAAgB;EACf,IAAM,UAA6B;GAClC,IAAI,CAAC,EAAW,SAAS;GACzB,IAAM,IAAc,SAAS,eAAe,CAAS;GAChD,MAEL,EAAuB,CAAW,GAElC,4BAA4B;IAC3B,IAAM,IAAO,EAAY,sBAAsB,GACzC,IAAgB,OAAO,YACvB,IAAiB,OAAO,aAE1B,IAAiB;IAyBrB,AAvBI,MAAgB,UAAU,EAAK,OAAO,IACzC,IAAiB,UACP,MAAgB,WAAW,EAAK,QAAQ,IAClD,IAAiB,SACP,MAAgB,SAAS,EAAK,MAAM,IAC9C,IAAiB,WACP,MAAgB,YAAY,EAAK,SAAS,MACpD,IAAiB,QAGd,MAAmB,WAAW,EAAK,QAAQ,IAC9C,IAAiB,SACP,MAAmB,UAAU,EAAK,OAAO,IACnD,IAAiB,UAEjB,MAAmB,YACnB,EAAK,SAAS,IAEd,IAAiB,QACP,MAAmB,SAAS,EAAK,MAAM,MACjD,IAAiB,WAGlB,EAAuB,CAAc;GACtC,CAAC;EACF,GAEM,IAAU,EAAW;EAC3B,IAAI,CAAC,GAAS;EAEd,IAAM,UAAyB;GAC9B,WAAW,GAAsB,CAAC;EACnC;EAKA,OAHA,EAAQ,iBAAiB,cAAc,CAAgB,GACvD,OAAO,iBAAiB,UAAU,CAAoB,SAEzC;GAEZ,AADA,EAAQ,oBAAoB,cAAc,CAAgB,GAC1D,OAAO,oBAAoB,UAAU,CAAoB;EAC1D;CACD,GAAG,CAAC,GAAa,CAAS,CAAC,GAK1B,kBAAC,OAAD;EAAK,WAAW,gBAHiB,EAAK,GAAG,EAAoB,GAAG,IAGtC,KAAK;EAAG,GAAI;EAAM,KAAK;YAAjD,CACE,GAED,kBAAC,OAAD;GACC,WAAU;GACV,IAAI;GACJ,yBAAyB,EAAE,QAAQ,EAAoB,CAAI,EAAE;EAC7D,CAAA,CACG;;AAEP"}
@@ -2,46 +2,45 @@ import { badResponses as e } from "../templates/badResponses.js";
2
2
  import { successResponses as t } from "../templates/successResponses.js";
3
3
  import { useModal as n } from "./useModal.js";
4
4
  import { useToast as r } from "./useToast.js";
5
- import { useCallback as i, useEffect as a } from "react";
6
- import { scroller as o } from "react-scroll";
5
+ import { useEffect as i } from "react";
6
+ import { scroller as a } from "react-scroll";
7
7
  //#region src/hooks/useAutomation.ts
8
- function s(s) {
9
- let { closeAll: c } = n(), { showToast: l } = r(), u = s?.closeModal, d = s?.message, f = s?.name, p = s?.cause?.data?.scrollTo, m = s?.cause?.fieldErrors ? Object.values(s?.cause?.fieldErrors)[0] : null, h = i(() => {
10
- if (!(!d && !m)) {
11
- if (t.includes(f)) return l({
12
- message: d,
13
- type: "success"
14
- });
15
- if (e.includes(f)) {
16
- if (m) return l({
17
- message: m,
18
- type: "danger"
8
+ function o(o) {
9
+ let { closeAll: s } = n(), { showToast: c } = r();
10
+ i(() => {
11
+ let n = o?.closeModal, r = o?.message, i = o?.name, l = o?.cause?.data?.scrollTo, u = o?.cause?.fieldErrors ? Object.values(o?.cause?.fieldErrors)[0] : null;
12
+ if (n && s(), l && a.scrollTo(l, {
13
+ smooth: !0,
14
+ offset: 20
15
+ }), !(!r && !u)) {
16
+ if (t.includes(i)) {
17
+ c({
18
+ message: r,
19
+ type: "success"
19
20
  });
20
- if (d !== "Unprocessable entity") return l({
21
- message: d,
21
+ return;
22
+ }
23
+ if (e.includes(i)) {
24
+ if (u) {
25
+ c({
26
+ message: u,
27
+ type: "danger"
28
+ });
29
+ return;
30
+ }
31
+ r !== "Unprocessable entity" && c({
32
+ message: r,
22
33
  type: "danger"
23
34
  });
24
35
  }
25
36
  }
26
37
  }, [
27
- d,
28
- m,
29
- f,
30
- l
31
- ]);
32
- a(() => {
33
- u && c(), p && o.scrollTo(p, {
34
- smooth: !0,
35
- offset: 20
36
- }), h();
37
- }, [
38
- u,
39
- p,
40
- h,
38
+ o,
39
+ s,
41
40
  c
42
41
  ]);
43
42
  }
44
43
  //#endregion
45
- export { s as useAutomation };
44
+ export { o as useAutomation };
46
45
 
47
46
  //# sourceMappingURL=useAutomation.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useAutomation.js","names":[],"sources":["../../../src/hooks/useAutomation.ts"],"sourcesContent":["import { useCallback, useEffect } from \"react\";\nimport { scroller } from \"react-scroll\";\nimport { badResponses } from \"../templates/badResponses\";\nimport { successResponses } from \"../templates/successResponses\";\nimport { useModal } from \"./useModal\";\nimport { useToast } from \"./useToast\";\n\n/**\n * useAutomation, runs UI side-effects (close modals, scroll, toast) in response to a server action payload.\n *\n * Pass the raw response from a form submission. On every change the hook:\n * 1. Closes all open modals if `closeModal` is `true`.\n * 2. Smooth-scrolls to a named element if `cause.data.scrollTo` is set.\n * 3. Shows a toast based on `name` (matched against `successResponses`/`badResponses`) and `message`.\n * When `cause.fieldErrors` is present, the first field error takes priority in the toast.\n * The message `\"Unprocessable entity\"` is intentionally suppressed.\n *\n * @param formResponseData - Raw server response payload.\n * @param formResponseData.closeModal - Closes all open modals when `true`.\n * @param formResponseData.name - Response identifier matched against success/bad response lists.\n * @param formResponseData.message - Text shown in the toast notification.\n * @param formResponseData.cause.data.scrollTo - Element name to scroll to via `react-scroll`.\n * @param formResponseData.cause.fieldErrors - Field-level errors; the first value is shown in the toast.\n *\n * @example\n * ```tsx\n * // After a successful create action\n * useAutomation({ closeModal: true, name: \"created\", message: \"Saved successfully\" });\n * ```\n *\n * @example\n * ```tsx\n * // Validation error with field-level feedback and scroll\n * useAutomation({\n * name: \"validation_error\",\n * message: \"Invalid payload\",\n * cause: {\n * data: { scrollTo: \"email\" },\n * fieldErrors: { email: \"E-mail is required\" },\n * },\n * });\n * ```\n */\n\n// biome-ignore lint/suspicious/noExplicitAny: intentional\nfunction useAutomation(formResponseData: any) {\n\tconst { closeAll } = useModal();\n\tconst { showToast } = useToast();\n\n\tconst closeModal = formResponseData?.closeModal;\n\tconst message = formResponseData?.message;\n\tconst name = formResponseData?.name;\n\tconst scrollTo = formResponseData?.cause?.data?.scrollTo;\n\tconst firstErrorField = formResponseData?.cause?.fieldErrors\n\t\t? (Object.values(formResponseData?.cause?.fieldErrors)[0] as string)\n\t\t: null;\n\n\tconst fireToast = useCallback(() => {\n\t\tif (!message && !firstErrorField) return;\n\n\t\tif (successResponses.includes(name))\n\t\t\treturn showToast({ message, type: \"success\" });\n\n\t\tif (!badResponses.includes(name)) return;\n\t\tif (firstErrorField)\n\t\t\treturn showToast({ message: firstErrorField, type: \"danger\" });\n\n\t\tif (message === \"Unprocessable entity\") return;\n\t\treturn showToast({ message, type: \"danger\" });\n\t}, [message, firstErrorField, name, showToast]);\n\n\tuseEffect(() => {\n\t\tif (closeModal) closeAll();\n\t\tif (scrollTo) scroller.scrollTo(scrollTo, { smooth: true, offset: 20 });\n\t\tfireToast();\n\t}, [closeModal, scrollTo, fireToast, closeAll]);\n}\n\nexport { useAutomation };\n"],"mappings":";;;;;;;AA6CA,SAAS,EAAc,GAAuB;CAC7C,IAAM,EAAE,gBAAa,EAAS,GACxB,EAAE,iBAAc,EAAS,GAEzB,IAAa,GAAkB,YAC/B,IAAU,GAAkB,SAC5B,IAAO,GAAkB,MACzB,IAAW,GAAkB,OAAO,MAAM,UAC1C,IAAkB,GAAkB,OAAO,cAC7C,OAAO,OAAO,GAAkB,OAAO,WAAW,CAAC,CAAC,KACrD,MAEG,IAAY,QAAkB;EAC/B,OAAC,KAAW,CAAC,IAEjB;OAAI,EAAiB,SAAS,CAAI,GACjC,OAAO,EAAU;IAAE;IAAS,MAAM;GAAU,CAAC;GAEzC,MAAa,SAAS,CAAI,GAC/B;QAAI,GACH,OAAO,EAAU;KAAE,SAAS;KAAiB,MAAM;IAAS,CAAC;IAE1D,UAAY,wBAChB,OAAO,EAAU;KAAE;KAAS,MAAM;IAAS,CAAC;GAHkB;EAJhB;CAQ/C,GAAG;EAAC;EAAS;EAAiB;EAAM;CAAS,CAAC;CAE9C,QAAgB;EAGf,AAFI,KAAY,EAAS,GACrB,KAAU,EAAS,SAAS,GAAU;GAAE,QAAQ;GAAM,QAAQ;EAAG,CAAC,GACtE,EAAU;CACX,GAAG;EAAC;EAAY;EAAU;EAAW;CAAQ,CAAC;AAC/C"}
1
+ {"version":3,"file":"useAutomation.js","names":[],"sources":["../../../src/hooks/useAutomation.ts"],"sourcesContent":["import { useEffect } from \"react\";\nimport { scroller } from \"react-scroll\";\nimport { badResponses } from \"../templates/badResponses\";\nimport { successResponses } from \"../templates/successResponses\";\nimport { useModal } from \"./useModal\";\nimport { useToast } from \"./useToast\";\n\n/**\n * useAutomation, runs UI side-effects (close modals, scroll, toast) in response to a server action payload.\n *\n * Pass the raw response from a form submission. On every change the hook:\n * 1. Closes all open modals if `closeModal` is `true`.\n * 2. Smooth-scrolls to a named element if `cause.data.scrollTo` is set.\n * 3. Shows a toast based on `name` (matched against `successResponses`/`badResponses`) and `message`.\n * When `cause.fieldErrors` is present, the first field error takes priority in the toast.\n * The message `\"Unprocessable entity\"` is intentionally suppressed.\n *\n * @param formResponseData - Raw server response payload.\n * @param formResponseData.closeModal - Closes all open modals when `true`.\n * @param formResponseData.name - Response identifier matched against success/bad response lists.\n * @param formResponseData.message - Text shown in the toast notification.\n * @param formResponseData.cause.data.scrollTo - Element name to scroll to via `react-scroll`.\n * @param formResponseData.cause.fieldErrors - Field-level errors; the first value is shown in the toast.\n *\n * @example\n * ```tsx\n * // After a successful create action\n * useAutomation({ closeModal: true, name: \"created\", message: \"Saved successfully\" });\n * ```\n *\n * @example\n * ```tsx\n * // Validation error with field-level feedback and scroll\n * useAutomation({\n * name: \"validation_error\",\n * message: \"Invalid payload\",\n * cause: {\n * data: { scrollTo: \"email\" },\n * fieldErrors: { email: \"E-mail is required\" },\n * },\n * });\n * ```\n */\n\n// biome-ignore lint/suspicious/noExplicitAny: intentional\nfunction useAutomation(formResponseData: any) {\n\tconst { closeAll } = useModal();\n\tconst { showToast } = useToast();\n\n\tuseEffect(() => {\n\t\tconst closeModal = formResponseData?.closeModal;\n\t\tconst message = formResponseData?.message;\n\t\tconst name = formResponseData?.name;\n\t\tconst scrollTo = formResponseData?.cause?.data?.scrollTo;\n\t\tconst firstErrorField = formResponseData?.cause?.fieldErrors\n\t\t\t? (Object.values(formResponseData?.cause?.fieldErrors)[0] as string)\n\t\t\t: null;\n\n\t\tif (closeModal) closeAll();\n\t\tif (scrollTo) scroller.scrollTo(scrollTo, { smooth: true, offset: 20 });\n\n\t\tif (!message && !firstErrorField) return;\n\n\t\tif (successResponses.includes(name)) {\n\t\t\tshowToast({ message, type: \"success\" });\n\t\t\treturn;\n\t\t}\n\n\t\tif (!badResponses.includes(name)) return;\n\t\tif (firstErrorField) {\n\t\t\tshowToast({ message: firstErrorField, type: \"danger\" });\n\t\t\treturn;\n\t\t}\n\n\t\tif (message === \"Unprocessable entity\") return;\n\t\tshowToast({ message, type: \"danger\" });\n\t}, [formResponseData, closeAll, showToast]);\n}\n\nexport { useAutomation };\n"],"mappings":";;;;;;;AA6CA,SAAS,EAAc,GAAuB;CAC7C,IAAM,EAAE,gBAAa,EAAS,GACxB,EAAE,iBAAc,EAAS;CAE/B,QAAgB;EACf,IAAM,IAAa,GAAkB,YAC/B,IAAU,GAAkB,SAC5B,IAAO,GAAkB,MACzB,IAAW,GAAkB,OAAO,MAAM,UAC1C,IAAkB,GAAkB,OAAO,cAC7C,OAAO,OAAO,GAAkB,OAAO,WAAW,CAAC,CAAC,KACrD;EAEH,IAAI,KAAY,EAAS,GACrB,KAAU,EAAS,SAAS,GAAU;GAAE,QAAQ;GAAM,QAAQ;EAAG,CAAC,GAElE,GAAC,KAAW,CAAC,IAEjB;OAAI,EAAiB,SAAS,CAAI,GAAG;IACpC,EAAU;KAAE;KAAS,MAAM;IAAU,CAAC;IACtC;GACD;GAEK,MAAa,SAAS,CAAI,GAC/B;QAAI,GAAiB;KACpB,EAAU;MAAE,SAAS;MAAiB,MAAM;KAAS,CAAC;KACtD;IACD;IAEI,MAAY,0BAChB,EAAU;KAAE;KAAS,MAAM;IAAS,CAAC;GAHrC;EANA;CAUD,GAAG;EAAC;EAAkB;EAAU;CAAS,CAAC;AAC3C"}
@@ -0,0 +1,29 @@
1
+ import { filterXSS as e } from "xss";
2
+ //#region src/utils/sanitizeTooltipHtml.ts
3
+ var t = Object.fromEntries([
4
+ "b",
5
+ "strong",
6
+ "i",
7
+ "em",
8
+ "u",
9
+ "br",
10
+ "span",
11
+ "small"
12
+ ].map((e) => [e, []]));
13
+ function n(n) {
14
+ return e(n, {
15
+ whiteList: t,
16
+ stripIgnoreTagBody: [
17
+ "script",
18
+ "style",
19
+ "iframe",
20
+ "object",
21
+ "embed",
22
+ "noscript"
23
+ ]
24
+ });
25
+ }
26
+ //#endregion
27
+ export { n as sanitizeTooltipHtml };
28
+
29
+ //# sourceMappingURL=sanitizeTooltipHtml.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitizeTooltipHtml.js","names":[],"sources":["../../../src/utils/sanitizeTooltipHtml.ts"],"sourcesContent":["import { filterXSS } from \"xss\";\n\nconst ALLOWED_TAGS = [\"b\", \"strong\", \"i\", \"em\", \"u\", \"br\", \"span\", \"small\"];\n\n// No attributes are allowed on any tag, which removes the need to\n// separately vet href/src/style values.\nconst WHITE_LIST = Object.fromEntries(ALLOWED_TAGS.map((tag) => [tag, []]));\n\n/**\n * sanitizeTooltipHtml, strips any markup outside a small inline-formatting\n * allowlist so `Tooltip`'s `text` prop can safely accept HTML from\n * consumers without executing scripts or attribute-based payloads\n * (`<script>`, `onerror`, `onclick`, `javascript:` URLs, etc.).\n *\n * Uses `xss` rather than `sanitize-html`: both are real, well-established\n * HTML sanitizers, but `sanitize-html` pulls in `postcss` (for `style`\n * attribute parsing) which assumes a Node `process` global and breaks when\n * this package is bundled for a real-browser test/runtime environment.\n * `xss` has no such dependency and explicitly supports running in a\n * browser.\n */\nfunction sanitizeTooltipHtml(text: string): string {\n\treturn filterXSS(text, {\n\t\twhiteList: WHITE_LIST,\n\t\tstripIgnoreTagBody: [\n\t\t\t\"script\",\n\t\t\t\"style\",\n\t\t\t\"iframe\",\n\t\t\t\"object\",\n\t\t\t\"embed\",\n\t\t\t\"noscript\",\n\t\t],\n\t});\n}\n\nexport { sanitizeTooltipHtml };\n"],"mappings":";;AAMA,IAAM,IAAa,OAAO,YAAY;CAJhB;CAAK;CAAU;CAAK;CAAM;CAAK;CAAM;CAAQ;AAI7B,CAAA,CAAa,KAAK,MAAQ,CAAC,GAAK,CAAC,CAAC,CAAC,CAAC;AAe1E,SAAS,EAAoB,GAAsB;CAClD,OAAO,EAAU,GAAM;EACtB,WAAW;EACX,oBAAoB;GACnB;GACA;GACA;GACA;GACA;GACA;EACD;CACD,CAAC;AACF"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * sanitizeTooltipHtml, strips any markup outside a small inline-formatting
3
+ * allowlist so `Tooltip`'s `text` prop can safely accept HTML from
4
+ * consumers without executing scripts or attribute-based payloads
5
+ * (`<script>`, `onerror`, `onclick`, `javascript:` URLs, etc.).
6
+ *
7
+ * Uses `xss` rather than `sanitize-html`: both are real, well-established
8
+ * HTML sanitizers, but `sanitize-html` pulls in `postcss` (for `style`
9
+ * attribute parsing) which assumes a Node `process` global and breaks when
10
+ * this package is bundled for a real-browser test/runtime environment.
11
+ * `xss` has no such dependency and explicitly supports running in a
12
+ * browser.
13
+ */
14
+ declare function sanitizeTooltipHtml(text: string): string;
15
+ export { sanitizeTooltipHtml };
16
+ //# sourceMappingURL=sanitizeTooltipHtml.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitizeTooltipHtml.d.ts","sourceRoot":"","sources":["../../src/utils/sanitizeTooltipHtml.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;;GAYG;AACH,iBAAS,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAYjD;AAED,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkyn/components",
3
- "version": "3.0.10",
3
+ "version": "3.0.12",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -61,7 +61,8 @@
61
61
  ],
62
62
  "dependencies": {
63
63
  "@arkyn/shared": "^3.0.2",
64
- "@arkyn/templates": "^3.0.2"
64
+ "@arkyn/templates": "^3.0.2",
65
+ "xss": "^1.0.15"
65
66
  },
66
67
  "peerDependencies": {
67
68
  "@react-google-maps/api": ">=2.0.0",