@tribepad/themis 1.6.5 → 1.6.7
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.
- package/CHANGELOG.md +14 -0
- package/dist/elements/AlertDialog/index.js +1 -1
- package/dist/elements/AlertDialog/index.js.map +1 -1
- package/dist/elements/AlertDialog/index.mjs +1 -1
- package/dist/elements/AlertDialog/index.mjs.map +1 -1
- package/dist/elements/Breadcrumbs/index.js +1 -1
- package/dist/elements/Breadcrumbs/index.js.map +1 -1
- package/dist/elements/Breadcrumbs/index.mjs +1 -1
- package/dist/elements/Breadcrumbs/index.mjs.map +1 -1
- package/dist/elements/Combobox/Combobox.d.ts.map +1 -1
- package/dist/elements/Combobox/index.js +1 -1
- package/dist/elements/Combobox/index.js.map +1 -1
- package/dist/elements/Combobox/index.mjs +1 -1
- package/dist/elements/Combobox/index.mjs.map +1 -1
- package/dist/elements/DatePicker/DatePicker.d.ts.map +1 -1
- package/dist/elements/DatePicker/DateRangePicker.d.ts.map +1 -1
- package/dist/elements/DatePicker/index.js +1 -1
- package/dist/elements/DatePicker/index.js.map +1 -1
- package/dist/elements/DatePicker/index.mjs +1 -1
- package/dist/elements/DatePicker/index.mjs.map +1 -1
- package/dist/elements/Dropdown/Dropdown.d.ts.map +1 -1
- package/dist/elements/Dropdown/index.js +1 -1
- package/dist/elements/Dropdown/index.js.map +1 -1
- package/dist/elements/Dropdown/index.mjs +1 -1
- package/dist/elements/Dropdown/index.mjs.map +1 -1
- package/dist/elements/Filters/FilterPopover.d.ts.map +1 -1
- package/dist/elements/Filters/index.js +1 -1
- package/dist/elements/Filters/index.js.map +1 -1
- package/dist/elements/Filters/index.mjs +1 -1
- package/dist/elements/Filters/index.mjs.map +1 -1
- package/dist/elements/Modal/Modal.d.ts +1 -1
- package/dist/elements/Modal/Modal.d.ts.map +1 -1
- package/dist/elements/Modal/Modal.types.d.ts +2 -0
- package/dist/elements/Modal/Modal.types.d.ts.map +1 -1
- package/dist/elements/Modal/index.js +1 -1
- package/dist/elements/Modal/index.js.map +1 -1
- package/dist/elements/Modal/index.mjs +1 -1
- package/dist/elements/Modal/index.mjs.map +1 -1
- package/dist/elements/NumberField/NumberField.types.d.ts +1 -1
- package/dist/elements/NumberField/NumberField.types.d.ts.map +1 -1
- package/dist/elements/NumberField/index.js +1 -1
- package/dist/elements/NumberField/index.js.map +1 -1
- package/dist/elements/NumberField/index.mjs +1 -1
- package/dist/elements/NumberField/index.mjs.map +1 -1
- package/dist/elements/PageTitle/index.js +1 -1
- package/dist/elements/PageTitle/index.js.map +1 -1
- package/dist/elements/PageTitle/index.mjs +1 -1
- package/dist/elements/PageTitle/index.mjs.map +1 -1
- package/dist/elements/Panel/Panel.d.ts.map +1 -1
- package/dist/elements/Panel/index.js +1 -1
- package/dist/elements/Panel/index.js.map +1 -1
- package/dist/elements/Panel/index.mjs +1 -1
- package/dist/elements/Panel/index.mjs.map +1 -1
- package/dist/elements/Resizable/components/ResizablePopover.d.ts.map +1 -1
- package/dist/elements/Resizable/index.js +1 -1
- package/dist/elements/Resizable/index.js.map +1 -1
- package/dist/elements/Resizable/index.mjs +1 -1
- package/dist/elements/Resizable/index.mjs.map +1 -1
- package/dist/elements/Select/Select.d.ts.map +1 -1
- package/dist/elements/Select/index.js +1 -1
- package/dist/elements/Select/index.js.map +1 -1
- package/dist/elements/Select/index.mjs +1 -1
- package/dist/elements/Select/index.mjs.map +1 -1
- package/dist/elements/Sidebar/SidebarMobileDrawer.d.ts.map +1 -1
- package/dist/elements/Sidebar/index.js +1 -1
- package/dist/elements/Sidebar/index.js.map +1 -1
- package/dist/elements/Sidebar/index.mjs +1 -1
- package/dist/elements/Sidebar/index.mjs.map +1 -1
- package/dist/elements/Table/index.js +1 -1
- package/dist/elements/Table/index.js.map +1 -1
- package/dist/elements/Table/index.mjs +1 -1
- package/dist/elements/Table/index.mjs.map +1 -1
- package/dist/elements/Wizard/index.js +1 -1
- package/dist/elements/Wizard/index.js.map +1 -1
- package/dist/elements/Wizard/index.mjs +1 -1
- package/dist/elements/Wizard/index.mjs.map +1 -1
- package/dist/elements/index.js +1 -1
- package/dist/elements/index.js.map +1 -1
- package/dist/elements/index.mjs +1 -1
- package/dist/elements/index.mjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/utils/browserExtensionOverlay.d.ts +36 -0
- package/dist/utils/browserExtensionOverlay.d.ts.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/index.mjs +1 -1
- package/dist/utils/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/elements/NumberField/NumberField.stories.tsx +2 -2
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detection + dismissal guard for in-page writing-assistant browser extensions
|
|
3
|
+
* (Grammarly, LanguageTool, Wordtune, …).
|
|
4
|
+
*
|
|
5
|
+
* These extensions inject their suggestion UI as elements appended to `<body>`,
|
|
6
|
+
* outside any open overlay's DOM subtree. React Aria's `isDismissable` behaviour
|
|
7
|
+
* treats a click on that injected UI as an "outside click" and dismisses the
|
|
8
|
+
* overlay — so clicking a spelling suggestion silently closes the form pop-up
|
|
9
|
+
* and discards the user's in-progress correction.
|
|
10
|
+
*
|
|
11
|
+
* The guard below lets overlays keep open when the interaction lands on an
|
|
12
|
+
* extension-injected node, while every other outside click, Escape, and the
|
|
13
|
+
* close button behave exactly as before.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Returns `true` when `element` belongs to an injected in-page writing
|
|
17
|
+
* assistant (Grammarly, LanguageTool, Wordtune, …).
|
|
18
|
+
*
|
|
19
|
+
* UX-ONLY heuristic. This is a deliberately spoofable DOM sniff used solely to
|
|
20
|
+
* keep a form pop-up open when a writing-assistant suggestion is clicked. It is
|
|
21
|
+
* NOT a security/authorization control and must never gate access to data or
|
|
22
|
+
* actions (the frontend is not a security boundary). Pure function — never log
|
|
23
|
+
* the element (it can reference arbitrary DOM / user content).
|
|
24
|
+
*/
|
|
25
|
+
export declare function isExtensionInjectedElement(element: Element): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Builds a `shouldCloseOnInteractOutside` predicate for React Aria overlays
|
|
28
|
+
* that keeps the overlay open when the interaction targets an extension-injected
|
|
29
|
+
* element, while preserving any consumer-supplied predicate.
|
|
30
|
+
*
|
|
31
|
+
* React Aria semantics: the predicate returns `true` ⇒ the overlay should
|
|
32
|
+
* close. So we close only when the element is NOT extension-injected AND the
|
|
33
|
+
* consumer (if any) also agrees it should close.
|
|
34
|
+
*/
|
|
35
|
+
export declare function createInteractOutsideGuard(consumerShouldClose?: (element: Element) => boolean): (element: Element) => boolean;
|
|
36
|
+
//# sourceMappingURL=browserExtensionOverlay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browserExtensionOverlay.d.ts","sourceRoot":"","sources":["../../src/utils/browserExtensionOverlay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAwCpE;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CACxC,mBAAmB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAClD,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAG/B"}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1B,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,GAC3B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/utils/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
'use strict';var clsx=require('clsx'),tailwindMerge=require('tailwind-merge');function
|
|
1
|
+
'use strict';var clsx=require('clsx'),tailwindMerge=require('tailwind-merge');function i(...t){return tailwindMerge.twMerge(clsx.clsx(t))}function u(t){if(!(t instanceof Element))return false;let e=t;for(let o=0;e&&o<100;o++){let a=e.tagName?.toLowerCase()??"";if(a.startsWith("grammarly-")||a.startsWith("lt-")||e.hasAttribute("data-grammarly-shadow-root"))return true;if(e.parentElement)e=e.parentElement;else {let l=e.getRootNode();e=l instanceof ShadowRoot?l.host:null;}}let n=t;for(;n&&!n.tagName.includes("-");)n=n.parentElement;if(!n)return false;let r=n.parentElement;return r===document.body||r===document.documentElement}function f(t){return e=>!u(e)&&(t?.(e)??true)}function c(t,e){return !(t===false||e)}exports.cn=i;exports.createInteractOutsideGuard=f;exports.isExtensionInjectedElement=u;exports.shouldAnimate=c;//# sourceMappingURL=index.js.map
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/utils/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/cn.ts","../../src/utils/shouldAnimate.ts"],"names":["cn","inputs","twMerge","clsx","shouldAnimate","animated","prefersReducedMotion"],"mappings":"8EAcO,SAASA,CAAAA,CAAAA,GAAMC,CAAAA,CAA8B,CAClD,OAAOC,
|
|
1
|
+
{"version":3,"sources":["../../src/utils/cn.ts","../../src/utils/browserExtensionOverlay.ts","../../src/utils/shouldAnimate.ts"],"names":["cn","inputs","twMerge","clsx","isExtensionInjectedElement","element","node","depth","tag","root","custom","parent","createInteractOutsideGuard","consumerShouldClose","shouldAnimate","animated","prefersReducedMotion"],"mappings":"8EAcO,SAASA,CAAAA,CAAAA,GAAMC,CAAAA,CAA8B,CAClD,OAAOC,sBAAQC,SAAAA,CAAKF,CAAM,CAAC,CAC7B,CCWO,SAASG,CAAAA,CAA2BC,CAAAA,CAA2B,CACpE,GAAI,EAAEA,CAAAA,YAAmB,OAAA,CAAA,CAAU,OAAO,MAAA,CAU1C,IAAIC,CAAAA,CAAuBD,EAC3B,IAAA,IAASE,CAAAA,CAAQ,CAAA,CAAGD,CAAAA,EAAQC,CAAAA,CAAQ,GAAA,CAAoBA,CAAAA,EAAAA,CAAS,CAC/D,IAAMC,CAAAA,CAAMF,CAAAA,CAAK,OAAA,EAAS,WAAA,EAAY,EAAK,EAAA,CAG3C,GAFIE,EAAI,UAAA,CAAW,YAAY,CAAA,EAC3BA,CAAAA,CAAI,UAAA,CAAW,KAAK,CAAA,EACpBF,CAAAA,CAAK,aAAa,4BAA4B,CAAA,CAAG,OAAO,KAAA,CAE5D,GAAIA,CAAAA,CAAK,aAAA,CACPA,CAAAA,CAAOA,EAAK,aAAA,CAAA,KACP,CACL,IAAMG,CAAAA,CAAOH,CAAAA,CAAK,WAAA,EAAY,CAC9BA,CAAAA,CAAOG,CAAAA,YAAgB,UAAA,CAAaA,CAAAA,CAAK,IAAA,CAAO,KAClD,CACF,CAQA,IAAIC,EAAyBL,CAAAA,CAC7B,KAAOK,CAAAA,EAAU,CAACA,CAAAA,CAAO,OAAA,CAAQ,QAAA,CAAS,GAAG,GAC3CA,CAAAA,CAASA,CAAAA,CAAO,aAAA,CAElB,GAAI,CAACA,CAAAA,CAAQ,OAAO,MAAA,CAEpB,IAAMC,CAAAA,CAASD,CAAAA,CAAO,aAAA,CACtB,OAAOC,CAAAA,GAAW,QAAA,CAAS,IAAA,EAAQA,CAAAA,GAAW,QAAA,CAAS,eACzD,CAWO,SAASC,CAAAA,CACdC,CAAAA,CAC+B,CAC/B,OAAQR,GACN,CAACD,CAAAA,CAA2BC,CAAO,CAAA,GAAMQ,CAAAA,GAAsBR,CAAO,CAAA,EAAK,IAAA,CAC/E,CCzEO,SAASS,CAAAA,CACdC,CAAAA,CACAC,CAAAA,CACS,CAET,OADI,EAAAD,CAAAA,GAAa,OACbC,CAAAA,CAEN","file":"index.js","sourcesContent":["/**\n * Class Name Utility\n * Merges Tailwind CSS classes with conflict resolution\n *\n * Combines clsx for conditional classes and tailwind-merge for deduplication\n *\n * @example\n * cn('px-2 py-1', 'px-4') // => 'py-1 px-4' (px-4 overrides px-2)\n * cn('text-red-500', condition && 'text-blue-500') // => conditional application\n */\n\nimport { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","/**\n * Detection + dismissal guard for in-page writing-assistant browser extensions\n * (Grammarly, LanguageTool, Wordtune, …).\n *\n * These extensions inject their suggestion UI as elements appended to `<body>`,\n * outside any open overlay's DOM subtree. React Aria's `isDismissable` behaviour\n * treats a click on that injected UI as an \"outside click\" and dismisses the\n * overlay — so clicking a spelling suggestion silently closes the form pop-up\n * and discards the user's in-progress correction.\n *\n * The guard below lets overlays keep open when the interaction lands on an\n * extension-injected node, while every other outside click, Escape, and the\n * close button behave exactly as before.\n */\n\nconst MAX_ANCESTOR_DEPTH = 100;\n\n/**\n * Returns `true` when `element` belongs to an injected in-page writing\n * assistant (Grammarly, LanguageTool, Wordtune, …).\n *\n * UX-ONLY heuristic. This is a deliberately spoofable DOM sniff used solely to\n * keep a form pop-up open when a writing-assistant suggestion is clicked. It is\n * NOT a security/authorization control and must never gate access to data or\n * actions (the frontend is not a security boundary). Pure function — never log\n * the element (it can reference arbitrary DOM / user content).\n */\nexport function isExtensionInjectedElement(element: Element): boolean {\n if (!(element instanceof Element)) return false;\n\n // 1. Known-vendor markers (authoritative). Walk up via parentElement, and\n // climb shadow boundaries via getRootNode().host so detection still finds\n // the <grammarly-extension> host if React Aria's shadowDOM flag is ever\n // enabled (it defaults to false today → the document-level listener\n // already receives the retargeted host, so the parentElement walk alone\n // suffices currently). Depth-bounded as a defensive guard against a\n // crafted/overridden getRootNode/host returning a cycle (which would\n // otherwise hang the tab — a client-side DoS).\n let node: Element | null = element;\n for (let depth = 0; node && depth < MAX_ANCESTOR_DEPTH; depth++) {\n const tag = node.tagName?.toLowerCase() ?? '';\n if (tag.startsWith('grammarly-')) return true; // Grammarly\n if (tag.startsWith('lt-')) return true; // LanguageTool\n if (node.hasAttribute('data-grammarly-shadow-root')) return true;\n\n if (node.parentElement) {\n node = node.parentElement;\n } else {\n const root = node.getRootNode();\n node = root instanceof ShadowRoot ? root.host : null;\n }\n }\n\n // 2. Structural fallback (best-effort, future-proofs the long tail).\n // Extensions inject their UI as a custom element (hyphenated tag name)\n // mounted as a top-level sibling of the React root — i.e. a direct child\n // of <body>/<html>. Our app never renders body-level custom elements, and\n // React Aria portals render plain <div>s, so this stays safe for any\n // future first-party web component nested inside the app tree.\n let custom: Element | null = element;\n while (custom && !custom.tagName.includes('-')) {\n custom = custom.parentElement;\n }\n if (!custom) return false;\n\n const parent = custom.parentElement;\n return parent === document.body || parent === document.documentElement;\n}\n\n/**\n * Builds a `shouldCloseOnInteractOutside` predicate for React Aria overlays\n * that keeps the overlay open when the interaction targets an extension-injected\n * element, while preserving any consumer-supplied predicate.\n *\n * React Aria semantics: the predicate returns `true` ⇒ the overlay should\n * close. So we close only when the element is NOT extension-injected AND the\n * consumer (if any) also agrees it should close.\n */\nexport function createInteractOutsideGuard(\n consumerShouldClose?: (element: Element) => boolean,\n): (element: Element) => boolean {\n return (element: Element): boolean =>\n !isExtensionInjectedElement(element) && (consumerShouldClose?.(element) ?? true);\n}\n","/**\n * Determines whether a component should animate.\n *\n * Returns `false` if:\n * - The `animated` prop is explicitly `false`\n * - The user prefers reduced motion\n *\n * @param animated - The component's `animated` prop value (default: `true`)\n * @param prefersReducedMotion - Whether the user prefers reduced motion\n */\nexport function shouldAnimate(\n animated: boolean | undefined,\n prefersReducedMotion: boolean,\n): boolean {\n if (animated === false) return false;\n if (prefersReducedMotion) return false;\n return true;\n}\n"]}
|
package/dist/utils/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {clsx}from'clsx';import {twMerge}from'tailwind-merge';function
|
|
1
|
+
import {clsx}from'clsx';import {twMerge}from'tailwind-merge';function i(...t){return twMerge(clsx(t))}function u(t){if(!(t instanceof Element))return false;let e=t;for(let o=0;e&&o<100;o++){let a=e.tagName?.toLowerCase()??"";if(a.startsWith("grammarly-")||a.startsWith("lt-")||e.hasAttribute("data-grammarly-shadow-root"))return true;if(e.parentElement)e=e.parentElement;else {let l=e.getRootNode();e=l instanceof ShadowRoot?l.host:null;}}let n=t;for(;n&&!n.tagName.includes("-");)n=n.parentElement;if(!n)return false;let r=n.parentElement;return r===document.body||r===document.documentElement}function f(t){return e=>!u(e)&&(t?.(e)??true)}function c(t,e){return !(t===false||e)}export{i as cn,f as createInteractOutsideGuard,u as isExtensionInjectedElement,c as shouldAnimate};//# sourceMappingURL=index.mjs.map
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/utils/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/cn.ts","../../src/utils/shouldAnimate.ts"],"names":["cn","inputs","twMerge","clsx","shouldAnimate","animated","prefersReducedMotion"],"mappings":"6DAcO,SAASA,CAAAA,CAAAA,GAAMC,CAAAA,CAA8B,CAClD,OAAOC,
|
|
1
|
+
{"version":3,"sources":["../../src/utils/cn.ts","../../src/utils/browserExtensionOverlay.ts","../../src/utils/shouldAnimate.ts"],"names":["cn","inputs","twMerge","clsx","isExtensionInjectedElement","element","node","depth","tag","root","custom","parent","createInteractOutsideGuard","consumerShouldClose","shouldAnimate","animated","prefersReducedMotion"],"mappings":"6DAcO,SAASA,CAAAA,CAAAA,GAAMC,CAAAA,CAA8B,CAClD,OAAOC,QAAQC,IAAAA,CAAKF,CAAM,CAAC,CAC7B,CCWO,SAASG,CAAAA,CAA2BC,CAAAA,CAA2B,CACpE,GAAI,EAAEA,CAAAA,YAAmB,OAAA,CAAA,CAAU,OAAO,MAAA,CAU1C,IAAIC,CAAAA,CAAuBD,EAC3B,IAAA,IAASE,CAAAA,CAAQ,CAAA,CAAGD,CAAAA,EAAQC,CAAAA,CAAQ,GAAA,CAAoBA,CAAAA,EAAAA,CAAS,CAC/D,IAAMC,CAAAA,CAAMF,CAAAA,CAAK,OAAA,EAAS,WAAA,EAAY,EAAK,EAAA,CAG3C,GAFIE,EAAI,UAAA,CAAW,YAAY,CAAA,EAC3BA,CAAAA,CAAI,UAAA,CAAW,KAAK,CAAA,EACpBF,CAAAA,CAAK,aAAa,4BAA4B,CAAA,CAAG,OAAO,KAAA,CAE5D,GAAIA,CAAAA,CAAK,aAAA,CACPA,CAAAA,CAAOA,EAAK,aAAA,CAAA,KACP,CACL,IAAMG,CAAAA,CAAOH,CAAAA,CAAK,WAAA,EAAY,CAC9BA,CAAAA,CAAOG,CAAAA,YAAgB,UAAA,CAAaA,CAAAA,CAAK,IAAA,CAAO,KAClD,CACF,CAQA,IAAIC,EAAyBL,CAAAA,CAC7B,KAAOK,CAAAA,EAAU,CAACA,CAAAA,CAAO,OAAA,CAAQ,QAAA,CAAS,GAAG,GAC3CA,CAAAA,CAASA,CAAAA,CAAO,aAAA,CAElB,GAAI,CAACA,CAAAA,CAAQ,OAAO,MAAA,CAEpB,IAAMC,CAAAA,CAASD,CAAAA,CAAO,aAAA,CACtB,OAAOC,CAAAA,GAAW,QAAA,CAAS,IAAA,EAAQA,CAAAA,GAAW,QAAA,CAAS,eACzD,CAWO,SAASC,CAAAA,CACdC,CAAAA,CAC+B,CAC/B,OAAQR,GACN,CAACD,CAAAA,CAA2BC,CAAO,CAAA,GAAMQ,CAAAA,GAAsBR,CAAO,CAAA,EAAK,IAAA,CAC/E,CCzEO,SAASS,CAAAA,CACdC,CAAAA,CACAC,CAAAA,CACS,CAET,OADI,EAAAD,CAAAA,GAAa,OACbC,CAAAA,CAEN","file":"index.mjs","sourcesContent":["/**\n * Class Name Utility\n * Merges Tailwind CSS classes with conflict resolution\n *\n * Combines clsx for conditional classes and tailwind-merge for deduplication\n *\n * @example\n * cn('px-2 py-1', 'px-4') // => 'py-1 px-4' (px-4 overrides px-2)\n * cn('text-red-500', condition && 'text-blue-500') // => conditional application\n */\n\nimport { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","/**\n * Detection + dismissal guard for in-page writing-assistant browser extensions\n * (Grammarly, LanguageTool, Wordtune, …).\n *\n * These extensions inject their suggestion UI as elements appended to `<body>`,\n * outside any open overlay's DOM subtree. React Aria's `isDismissable` behaviour\n * treats a click on that injected UI as an \"outside click\" and dismisses the\n * overlay — so clicking a spelling suggestion silently closes the form pop-up\n * and discards the user's in-progress correction.\n *\n * The guard below lets overlays keep open when the interaction lands on an\n * extension-injected node, while every other outside click, Escape, and the\n * close button behave exactly as before.\n */\n\nconst MAX_ANCESTOR_DEPTH = 100;\n\n/**\n * Returns `true` when `element` belongs to an injected in-page writing\n * assistant (Grammarly, LanguageTool, Wordtune, …).\n *\n * UX-ONLY heuristic. This is a deliberately spoofable DOM sniff used solely to\n * keep a form pop-up open when a writing-assistant suggestion is clicked. It is\n * NOT a security/authorization control and must never gate access to data or\n * actions (the frontend is not a security boundary). Pure function — never log\n * the element (it can reference arbitrary DOM / user content).\n */\nexport function isExtensionInjectedElement(element: Element): boolean {\n if (!(element instanceof Element)) return false;\n\n // 1. Known-vendor markers (authoritative). Walk up via parentElement, and\n // climb shadow boundaries via getRootNode().host so detection still finds\n // the <grammarly-extension> host if React Aria's shadowDOM flag is ever\n // enabled (it defaults to false today → the document-level listener\n // already receives the retargeted host, so the parentElement walk alone\n // suffices currently). Depth-bounded as a defensive guard against a\n // crafted/overridden getRootNode/host returning a cycle (which would\n // otherwise hang the tab — a client-side DoS).\n let node: Element | null = element;\n for (let depth = 0; node && depth < MAX_ANCESTOR_DEPTH; depth++) {\n const tag = node.tagName?.toLowerCase() ?? '';\n if (tag.startsWith('grammarly-')) return true; // Grammarly\n if (tag.startsWith('lt-')) return true; // LanguageTool\n if (node.hasAttribute('data-grammarly-shadow-root')) return true;\n\n if (node.parentElement) {\n node = node.parentElement;\n } else {\n const root = node.getRootNode();\n node = root instanceof ShadowRoot ? root.host : null;\n }\n }\n\n // 2. Structural fallback (best-effort, future-proofs the long tail).\n // Extensions inject their UI as a custom element (hyphenated tag name)\n // mounted as a top-level sibling of the React root — i.e. a direct child\n // of <body>/<html>. Our app never renders body-level custom elements, and\n // React Aria portals render plain <div>s, so this stays safe for any\n // future first-party web component nested inside the app tree.\n let custom: Element | null = element;\n while (custom && !custom.tagName.includes('-')) {\n custom = custom.parentElement;\n }\n if (!custom) return false;\n\n const parent = custom.parentElement;\n return parent === document.body || parent === document.documentElement;\n}\n\n/**\n * Builds a `shouldCloseOnInteractOutside` predicate for React Aria overlays\n * that keeps the overlay open when the interaction targets an extension-injected\n * element, while preserving any consumer-supplied predicate.\n *\n * React Aria semantics: the predicate returns `true` ⇒ the overlay should\n * close. So we close only when the element is NOT extension-injected AND the\n * consumer (if any) also agrees it should close.\n */\nexport function createInteractOutsideGuard(\n consumerShouldClose?: (element: Element) => boolean,\n): (element: Element) => boolean {\n return (element: Element): boolean =>\n !isExtensionInjectedElement(element) && (consumerShouldClose?.(element) ?? true);\n}\n","/**\n * Determines whether a component should animate.\n *\n * Returns `false` if:\n * - The `animated` prop is explicitly `false`\n * - The user prefers reduced motion\n *\n * @param animated - The component's `animated` prop value (default: `true`)\n * @param prefersReducedMotion - Whether the user prefers reduced motion\n */\nexport function shouldAnimate(\n animated: boolean | undefined,\n prefersReducedMotion: boolean,\n): boolean {\n if (animated === false) return false;\n if (prefersReducedMotion) return false;\n return true;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -682,7 +682,7 @@ export const Playground: Story = {
|
|
|
682
682
|
// =============================================================================
|
|
683
683
|
|
|
684
684
|
/**
|
|
685
|
-
* Spring animation on stepper press (
|
|
685
|
+
* Spring animation on stepper press (opt-in via `animated`; motion.dev must be installed).
|
|
686
686
|
* Click increment/decrement to see the value spring to its target.
|
|
687
687
|
*/
|
|
688
688
|
export const AnimatedStepper: Story = {
|
|
@@ -699,7 +699,7 @@ export const AnimatedStepper: Story = {
|
|
|
699
699
|
docs: {
|
|
700
700
|
description: {
|
|
701
701
|
story:
|
|
702
|
-
'
|
|
702
|
+
'NumberField with `animated` enabled — spring physics on stepper press. The displayed value springs from the old value to the new one. Direct keyboard input updates instantly. Animation is opt-in; the default is no spring.',
|
|
703
703
|
},
|
|
704
704
|
},
|
|
705
705
|
},
|