tempest-react-sdk 0.29.1 → 0.30.0
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/dist/components/Chat/Chat.cjs +2 -0
- package/dist/components/Chat/Chat.cjs.map +1 -0
- package/dist/components/Chat/Chat.js +126 -0
- package/dist/components/Chat/Chat.js.map +1 -0
- package/dist/components/Chat/Chat.module.cjs +2 -0
- package/dist/components/Chat/Chat.module.cjs.map +1 -0
- package/dist/components/Chat/Chat.module.js +32 -0
- package/dist/components/Chat/Chat.module.js.map +1 -0
- package/dist/components/Chat/ChatComposer.cjs +2 -0
- package/dist/components/Chat/ChatComposer.cjs.map +1 -0
- package/dist/components/Chat/ChatComposer.js +67 -0
- package/dist/components/Chat/ChatComposer.js.map +1 -0
- package/dist/components/Chat/chat-groups.cjs +2 -0
- package/dist/components/Chat/chat-groups.cjs.map +1 -0
- package/dist/components/Chat/chat-groups.js +89 -0
- package/dist/components/Chat/chat-groups.js.map +1 -0
- package/dist/components/Markdown/Markdown.cjs +2 -0
- package/dist/components/Markdown/Markdown.cjs.map +1 -0
- package/dist/components/Markdown/Markdown.js +124 -0
- package/dist/components/Markdown/Markdown.js.map +1 -0
- package/dist/components/Markdown/Markdown.module.cjs +2 -0
- package/dist/components/Markdown/Markdown.module.cjs.map +1 -0
- package/dist/components/Markdown/Markdown.module.js +23 -0
- package/dist/components/Markdown/Markdown.module.js.map +1 -0
- package/dist/components/Markdown/markdown-parse.cjs +7 -0
- package/dist/components/Markdown/markdown-parse.cjs.map +1 -0
- package/dist/components/Markdown/markdown-parse.js +269 -0
- package/dist/components/Markdown/markdown-parse.js.map +1 -0
- package/dist/components/Markdown/markdown-url.cjs +2 -0
- package/dist/components/Markdown/markdown-url.cjs.map +1 -0
- package/dist/components/Markdown/markdown-url.js +28 -0
- package/dist/components/Markdown/markdown-url.js.map +1 -0
- package/dist/components/Masonry/Masonry.cjs +2 -0
- package/dist/components/Masonry/Masonry.cjs.map +1 -0
- package/dist/components/Masonry/Masonry.js +70 -0
- package/dist/components/Masonry/Masonry.js.map +1 -0
- package/dist/components/Masonry/Masonry.module.cjs +2 -0
- package/dist/components/Masonry/Masonry.module.cjs.map +1 -0
- package/dist/components/Masonry/Masonry.module.js +10 -0
- package/dist/components/Masonry/Masonry.module.js.map +1 -0
- package/dist/components/Masonry/masonry-layout.cjs +2 -0
- package/dist/components/Masonry/masonry-layout.cjs.map +1 -0
- package/dist/components/Masonry/masonry-layout.js +20 -0
- package/dist/components/Masonry/masonry-layout.js.map +1 -0
- package/dist/components/Transfer/Transfer.cjs +2 -0
- package/dist/components/Transfer/Transfer.cjs.map +1 -0
- package/dist/components/Transfer/Transfer.js +163 -0
- package/dist/components/Transfer/Transfer.js.map +1 -0
- package/dist/components/Transfer/Transfer.module.cjs +2 -0
- package/dist/components/Transfer/Transfer.module.cjs.map +1 -0
- package/dist/components/Transfer/Transfer.module.js +19 -0
- package/dist/components/Transfer/Transfer.module.js.map +1 -0
- package/dist/components/Transfer/transfer-state.cjs +2 -0
- package/dist/components/Transfer/transfer-state.cjs.map +1 -0
- package/dist/components/Transfer/transfer-state.js +59 -0
- package/dist/components/Transfer/transfer-state.js.map +1 -0
- package/dist/styles.css +1 -1
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +493 -0
- package/dist/tempest-react-sdk.js +114 -105
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Masonry.cjs","names":[],"sources":["../../../src/components/Masonry/Masonry.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { columnsFor, distribute } from \"./masonry-layout\";\nimport styles from \"./Masonry.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\";\n\nexport interface MasonryProps<T> extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** What to lay out. */\n items: readonly T[];\n /** Render one card. */\n children: (item: T, index: number) => ReactNode;\n /** Stable key per item. Index is a fallback nobody should rely on. */\n itemKey?: (item: T, index: number) => string | number;\n /**\n * Column count: a fixed number, or a `width → columns` map read as\n * \"from this width up\". Default `{ 0: 1, 640: 2, 1024: 3 }`.\n */\n columns?: number | Record<number, number>;\n /** Gap between cards, any CSS length. Default `var(--tempest-space-4)`. */\n gap?: string;\n}\n\n/** Default breakpoints: one column on a phone, three on a desktop. */\nconst DEFAULT_COLUMNS: Record<number, number> = { 0: 1, 640: 2, 1024: 3 };\n\n/**\n * Masonry layout: cards of uneven height packed into columns with an even bottom\n * edge.\n *\n * Measures the rendered cards and deals each one into the shortest column, rather\n * than using CSS `columns` or `grid-auto-flow: dense`. Both of those are one line\n * of CSS and neither does this job: CSS `columns` breaks a card across the column\n * boundary, and a dense grid keeps every row the height of its tallest cell, which\n * is the ragged bottom edge people reach for masonry to avoid.\n *\n * @example\n * <Masonry items={fotos} itemKey={(foto) => foto.id} columns={{ 0: 1, 700: 2, 1100: 4 }}>\n * {(foto) => <img src={foto.url} alt={foto.alt} />}\n * </Masonry>\n */\nexport function Masonry<T>({\n items,\n children,\n itemKey,\n columns = DEFAULT_COLUMNS,\n gap,\n className,\n style,\n ...rest\n}: MasonryProps<T>) {\n const wrapper = useRef<HTMLDivElement | null>(null);\n const cells = useRef<Map<number, HTMLElement>>(new Map());\n const [width, setWidth] = useState(0);\n const [heights, setHeights] = useState<number[]>([]);\n\n const columnCount = columnsFor(width, columns);\n\n /**\n * Track the container width, not the viewport.\n *\n * A masonry inside a drawer or a two-column page is narrower than the window,\n * and a media query would give it desktop columns at 300px wide. `ResizeObserver`\n * is what makes the breakpoint map mean \"this container\", which is the only\n * useful reading.\n */\n useEffect(() => {\n const node = wrapper.current;\n if (!node || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(([entry]) => {\n setWidth(entry.contentRect.width);\n });\n observer.observe(node);\n setWidth(node.getBoundingClientRect().width);\n return () => observer.disconnect();\n }, []);\n\n /**\n * Re-read every card's height, and store it only when something moved.\n *\n * The equality check is what stops the loop: writing a new array on every pass\n * would re-render, re-measure and write again. The first render lays out in\n * source order with weight 1 each — never blank — and this pass re-deals with\n * real numbers.\n */\n const measure = useCallback(() => {\n const next = items.map((_, index) => {\n const node = cells.current.get(index);\n return node ? node.getBoundingClientRect().height : 1;\n });\n setHeights((current) =>\n current.length === next.length && current.every((value, i) => value === next[i])\n ? current\n : next,\n );\n }, [items]);\n\n useLayoutEffect(measure, [measure, columnCount, width]);\n\n /**\n * Re-measure when a card changes size on its own — an image finishing its\n * download is the common one, and it is exactly the case a height measured at\n * mount gets wrong.\n */\n useEffect(() => {\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(measure);\n for (const node of cells.current.values()) observer.observe(node);\n return () => observer.disconnect();\n }, [measure, columnCount]);\n\n const layout = useMemo(\n () =>\n distribute(\n items.map((_, index) => heights[index] ?? 1),\n columnCount,\n ),\n [items, heights, columnCount],\n );\n\n return (\n <div\n ref={wrapper}\n className={cn(styles.wrapper, className)}\n style={{ ...style, ...(gap ? { \"--tempest-masonry-gap\": gap } : {}) }}\n {...rest}\n >\n {layout.map((columnItems, column) => (\n <div key={column} className={styles.column}>\n {columnItems.map((index) => {\n const item = items[index];\n return (\n <div\n key={itemKey ? itemKey(item, index) : index}\n className={styles.item}\n ref={(node) => {\n if (node) cells.current.set(index, node);\n else cells.current.delete(index);\n }}\n >\n {children(item, index)}\n </div>\n );\n })}\n </div>\n ))}\n </div>\n );\n}\n"],"mappings":"gKAoCA,IAAM,EAA0C,CAAE,EAAG,EAAG,IAAK,EAAG,KAAM,CAAE,EAiBxE,SAAgB,EAAW,CACvB,QACA,WACA,UACA,UAAU,EACV,MACA,YACA,QACA,GAAG,GACa,CAChB,IAAM,GAAA,EAAA,EAAA,OAAA,CAAwC,IAAI,EAC5C,GAAA,EAAA,EAAA,OAAA,CAAyC,IAAI,GAAK,EAClD,CAAC,EAAO,IAAA,EAAA,EAAA,SAAA,CAAqB,CAAC,EAC9B,CAAC,EAAS,IAAA,EAAA,EAAA,SAAA,CAAiC,CAAC,CAAC,EAE7C,EAAc,EAAA,WAAW,EAAO,CAAO,GAU7C,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAM,EAAO,EAAQ,QACrB,GAAI,CAAC,GAAQ,OAAO,eAAmB,IAAa,OACpD,IAAM,EAAW,IAAI,gBAAgB,CAAC,KAAW,CAC7C,EAAS,EAAM,YAAY,KAAK,CACpC,CAAC,EAGD,OAFA,EAAS,QAAQ,CAAI,EACrB,EAAS,EAAK,sBAAsB,CAAC,CAAC,KAAK,MAC9B,EAAS,WAAW,CACrC,EAAG,CAAC,CAAC,EAUL,IAAM,GAAA,EAAA,EAAA,YAAA,KAA4B,CAC9B,IAAM,EAAO,EAAM,KAAK,EAAG,IAAU,CACjC,IAAM,EAAO,EAAM,QAAQ,IAAI,CAAK,EACpC,OAAO,EAAO,EAAK,sBAAsB,CAAC,CAAC,OAAS,CACxD,CAAC,EACD,EAAY,GACR,EAAQ,SAAW,EAAK,QAAU,EAAQ,OAAO,EAAO,IAAM,IAAU,EAAK,EAAE,EACzE,EACA,CACV,CACJ,EAAG,CAAC,CAAK,CAAC,GAEV,EAAA,EAAA,gBAAA,CAAgB,EAAS,CAAC,EAAS,EAAa,CAAK,CAAC,GAOtD,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,OAAO,eAAmB,IAAa,OAC3C,IAAM,EAAW,IAAI,eAAe,CAAO,EAC3C,IAAK,IAAM,KAAQ,EAAM,QAAQ,OAAO,EAAG,EAAS,QAAQ,CAAI,EAChE,UAAa,EAAS,WAAW,CACrC,EAAG,CAAC,EAAS,CAAW,CAAC,EAEzB,IAAM,GAAA,EAAA,EAAA,QAAA,KAEE,EAAA,WACI,EAAM,KAAK,EAAG,IAAU,EAAQ,IAAU,CAAC,EAC3C,CACJ,EACJ,CAAC,EAAO,EAAS,CAAW,CAChC,EAEA,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,IAAK,EACL,UAAW,EAAA,GAAG,EAAA,QAAO,QAAS,CAAS,EACvC,MAAO,CAAE,GAAG,EAAO,GAAI,EAAM,CAAE,wBAAyB,CAAI,EAAI,CAAC,CAAG,EACpE,GAAI,WAEH,EAAO,KAAK,EAAa,KACtB,EAAA,EAAA,IAAA,CAAC,MAAD,CAAkB,UAAW,EAAA,QAAO,gBAC/B,EAAY,IAAK,GAAU,CACxB,IAAM,EAAO,EAAM,GACnB,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CAEI,UAAW,EAAA,QAAO,KAClB,IAAM,GAAS,CACP,EAAM,EAAM,QAAQ,IAAI,EAAO,CAAI,EAClC,EAAM,QAAQ,OAAO,CAAK,CACnC,WAEC,EAAS,EAAM,CAAK,CACpB,EARI,EAAU,EAAQ,EAAM,CAAK,EAAI,CAQrC,CAEb,CAAC,CACA,EAhBK,CAgBL,CACR,CACA,CAAA,CAEb"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { cn as e } from "../../utils/cn.js";
|
|
2
|
+
import { columnsFor as t, distribute as n } from "./masonry-layout.js";
|
|
3
|
+
import r from "./Masonry.module.js";
|
|
4
|
+
import { useCallback as i, useEffect as a, useLayoutEffect as o, useMemo as s, useRef as c, useState as l } from "react";
|
|
5
|
+
import { jsx as u } from "react/jsx-runtime";
|
|
6
|
+
//#region src/components/Masonry/Masonry.tsx
|
|
7
|
+
var d = {
|
|
8
|
+
0: 1,
|
|
9
|
+
640: 2,
|
|
10
|
+
1024: 3
|
|
11
|
+
};
|
|
12
|
+
function f({ items: f, children: p, itemKey: m, columns: h = d, gap: g, className: _, style: v, ...y }) {
|
|
13
|
+
let b = c(null), x = c(/* @__PURE__ */ new Map()), [S, C] = l(0), [w, T] = l([]), E = t(S, h);
|
|
14
|
+
a(() => {
|
|
15
|
+
let e = b.current;
|
|
16
|
+
if (!e || typeof ResizeObserver > "u") return;
|
|
17
|
+
let t = new ResizeObserver(([e]) => {
|
|
18
|
+
C(e.contentRect.width);
|
|
19
|
+
});
|
|
20
|
+
return t.observe(e), C(e.getBoundingClientRect().width), () => t.disconnect();
|
|
21
|
+
}, []);
|
|
22
|
+
let D = i(() => {
|
|
23
|
+
let e = f.map((e, t) => {
|
|
24
|
+
let n = x.current.get(t);
|
|
25
|
+
return n ? n.getBoundingClientRect().height : 1;
|
|
26
|
+
});
|
|
27
|
+
T((t) => t.length === e.length && t.every((t, n) => t === e[n]) ? t : e);
|
|
28
|
+
}, [f]);
|
|
29
|
+
o(D, [
|
|
30
|
+
D,
|
|
31
|
+
E,
|
|
32
|
+
S
|
|
33
|
+
]), a(() => {
|
|
34
|
+
if (typeof ResizeObserver > "u") return;
|
|
35
|
+
let e = new ResizeObserver(D);
|
|
36
|
+
for (let t of x.current.values()) e.observe(t);
|
|
37
|
+
return () => e.disconnect();
|
|
38
|
+
}, [D, E]);
|
|
39
|
+
let O = s(() => n(f.map((e, t) => w[t] ?? 1), E), [
|
|
40
|
+
f,
|
|
41
|
+
w,
|
|
42
|
+
E
|
|
43
|
+
]);
|
|
44
|
+
return /* @__PURE__ */ u("div", {
|
|
45
|
+
ref: b,
|
|
46
|
+
className: e(r.wrapper, _),
|
|
47
|
+
style: {
|
|
48
|
+
...v,
|
|
49
|
+
...g ? { "--tempest-masonry-gap": g } : {}
|
|
50
|
+
},
|
|
51
|
+
...y,
|
|
52
|
+
children: O.map((e, t) => /* @__PURE__ */ u("div", {
|
|
53
|
+
className: r.column,
|
|
54
|
+
children: e.map((e) => {
|
|
55
|
+
let t = f[e];
|
|
56
|
+
return /* @__PURE__ */ u("div", {
|
|
57
|
+
className: r.item,
|
|
58
|
+
ref: (t) => {
|
|
59
|
+
t ? x.current.set(e, t) : x.current.delete(e);
|
|
60
|
+
},
|
|
61
|
+
children: p(t, e)
|
|
62
|
+
}, m ? m(t, e) : e);
|
|
63
|
+
})
|
|
64
|
+
}, t))
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
export { f as Masonry };
|
|
69
|
+
|
|
70
|
+
//# sourceMappingURL=Masonry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Masonry.js","names":[],"sources":["../../../src/components/Masonry/Masonry.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { columnsFor, distribute } from \"./masonry-layout\";\nimport styles from \"./Masonry.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\";\n\nexport interface MasonryProps<T> extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** What to lay out. */\n items: readonly T[];\n /** Render one card. */\n children: (item: T, index: number) => ReactNode;\n /** Stable key per item. Index is a fallback nobody should rely on. */\n itemKey?: (item: T, index: number) => string | number;\n /**\n * Column count: a fixed number, or a `width → columns` map read as\n * \"from this width up\". Default `{ 0: 1, 640: 2, 1024: 3 }`.\n */\n columns?: number | Record<number, number>;\n /** Gap between cards, any CSS length. Default `var(--tempest-space-4)`. */\n gap?: string;\n}\n\n/** Default breakpoints: one column on a phone, three on a desktop. */\nconst DEFAULT_COLUMNS: Record<number, number> = { 0: 1, 640: 2, 1024: 3 };\n\n/**\n * Masonry layout: cards of uneven height packed into columns with an even bottom\n * edge.\n *\n * Measures the rendered cards and deals each one into the shortest column, rather\n * than using CSS `columns` or `grid-auto-flow: dense`. Both of those are one line\n * of CSS and neither does this job: CSS `columns` breaks a card across the column\n * boundary, and a dense grid keeps every row the height of its tallest cell, which\n * is the ragged bottom edge people reach for masonry to avoid.\n *\n * @example\n * <Masonry items={fotos} itemKey={(foto) => foto.id} columns={{ 0: 1, 700: 2, 1100: 4 }}>\n * {(foto) => <img src={foto.url} alt={foto.alt} />}\n * </Masonry>\n */\nexport function Masonry<T>({\n items,\n children,\n itemKey,\n columns = DEFAULT_COLUMNS,\n gap,\n className,\n style,\n ...rest\n}: MasonryProps<T>) {\n const wrapper = useRef<HTMLDivElement | null>(null);\n const cells = useRef<Map<number, HTMLElement>>(new Map());\n const [width, setWidth] = useState(0);\n const [heights, setHeights] = useState<number[]>([]);\n\n const columnCount = columnsFor(width, columns);\n\n /**\n * Track the container width, not the viewport.\n *\n * A masonry inside a drawer or a two-column page is narrower than the window,\n * and a media query would give it desktop columns at 300px wide. `ResizeObserver`\n * is what makes the breakpoint map mean \"this container\", which is the only\n * useful reading.\n */\n useEffect(() => {\n const node = wrapper.current;\n if (!node || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(([entry]) => {\n setWidth(entry.contentRect.width);\n });\n observer.observe(node);\n setWidth(node.getBoundingClientRect().width);\n return () => observer.disconnect();\n }, []);\n\n /**\n * Re-read every card's height, and store it only when something moved.\n *\n * The equality check is what stops the loop: writing a new array on every pass\n * would re-render, re-measure and write again. The first render lays out in\n * source order with weight 1 each — never blank — and this pass re-deals with\n * real numbers.\n */\n const measure = useCallback(() => {\n const next = items.map((_, index) => {\n const node = cells.current.get(index);\n return node ? node.getBoundingClientRect().height : 1;\n });\n setHeights((current) =>\n current.length === next.length && current.every((value, i) => value === next[i])\n ? current\n : next,\n );\n }, [items]);\n\n useLayoutEffect(measure, [measure, columnCount, width]);\n\n /**\n * Re-measure when a card changes size on its own — an image finishing its\n * download is the common one, and it is exactly the case a height measured at\n * mount gets wrong.\n */\n useEffect(() => {\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(measure);\n for (const node of cells.current.values()) observer.observe(node);\n return () => observer.disconnect();\n }, [measure, columnCount]);\n\n const layout = useMemo(\n () =>\n distribute(\n items.map((_, index) => heights[index] ?? 1),\n columnCount,\n ),\n [items, heights, columnCount],\n );\n\n return (\n <div\n ref={wrapper}\n className={cn(styles.wrapper, className)}\n style={{ ...style, ...(gap ? { \"--tempest-masonry-gap\": gap } : {}) }}\n {...rest}\n >\n {layout.map((columnItems, column) => (\n <div key={column} className={styles.column}>\n {columnItems.map((index) => {\n const item = items[index];\n return (\n <div\n key={itemKey ? itemKey(item, index) : index}\n className={styles.item}\n ref={(node) => {\n if (node) cells.current.set(index, node);\n else cells.current.delete(index);\n }}\n >\n {children(item, index)}\n </div>\n );\n })}\n </div>\n ))}\n </div>\n );\n}\n"],"mappings":";;;;;;AAoCA,IAAM,IAA0C;CAAE,GAAG;CAAG,KAAK;CAAG,MAAM;AAAE;AAiBxE,SAAgB,EAAW,EACvB,UACA,aACA,YACA,aAAU,GACV,QACA,cACA,UACA,GAAG,KACa;CAChB,IAAM,IAAU,EAA8B,IAAI,GAC5C,IAAQ,kBAAiC,IAAI,IAAI,CAAC,GAClD,CAAC,GAAO,KAAY,EAAS,CAAC,GAC9B,CAAC,GAAS,KAAc,EAAmB,CAAC,CAAC,GAE7C,IAAc,EAAW,GAAO,CAAO;CAU7C,QAAgB;EACZ,IAAM,IAAO,EAAQ;EACrB,IAAI,CAAC,KAAQ,OAAO,iBAAmB,KAAa;EACpD,IAAM,IAAW,IAAI,gBAAgB,CAAC,OAAW;GAC7C,EAAS,EAAM,YAAY,KAAK;EACpC,CAAC;EAGD,OAFA,EAAS,QAAQ,CAAI,GACrB,EAAS,EAAK,sBAAsB,CAAC,CAAC,KAAK,SAC9B,EAAS,WAAW;CACrC,GAAG,CAAC,CAAC;CAUL,IAAM,IAAU,QAAkB;EAC9B,IAAM,IAAO,EAAM,KAAK,GAAG,MAAU;GACjC,IAAM,IAAO,EAAM,QAAQ,IAAI,CAAK;GACpC,OAAO,IAAO,EAAK,sBAAsB,CAAC,CAAC,SAAS;EACxD,CAAC;EACD,GAAY,MACR,EAAQ,WAAW,EAAK,UAAU,EAAQ,OAAO,GAAO,MAAM,MAAU,EAAK,EAAE,IACzE,IACA,CACV;CACJ,GAAG,CAAC,CAAK,CAAC;CASV,AAPA,EAAgB,GAAS;EAAC;EAAS;EAAa;CAAK,CAAC,GAOtD,QAAgB;EACZ,IAAI,OAAO,iBAAmB,KAAa;EAC3C,IAAM,IAAW,IAAI,eAAe,CAAO;EAC3C,KAAK,IAAM,KAAQ,EAAM,QAAQ,OAAO,GAAG,EAAS,QAAQ,CAAI;EAChE,aAAa,EAAS,WAAW;CACrC,GAAG,CAAC,GAAS,CAAW,CAAC;CAEzB,IAAM,IAAS,QAEP,EACI,EAAM,KAAK,GAAG,MAAU,EAAQ,MAAU,CAAC,GAC3C,CACJ,GACJ;EAAC;EAAO;EAAS;CAAW,CAChC;CAEA,OACI,kBAAC,OAAD;EACI,KAAK;EACL,WAAW,EAAG,EAAO,SAAS,CAAS;EACvC,OAAO;GAAE,GAAG;GAAO,GAAI,IAAM,EAAE,yBAAyB,EAAI,IAAI,CAAC;EAAG;EACpE,GAAI;YAEH,EAAO,KAAK,GAAa,MACtB,kBAAC,OAAD;GAAkB,WAAW,EAAO;aAC/B,EAAY,KAAK,MAAU;IACxB,IAAM,IAAO,EAAM;IACnB,OACI,kBAAC,OAAD;KAEI,WAAW,EAAO;KAClB,MAAM,MAAS;MACX,AAAI,IAAM,EAAM,QAAQ,IAAI,GAAO,CAAI,IAClC,EAAM,QAAQ,OAAO,CAAK;KACnC;eAEC,EAAS,GAAM,CAAK;IACpB,GARI,IAAU,EAAQ,GAAM,CAAK,IAAI,CAQrC;GAEb,CAAC;EACA,GAhBK,CAgBL,CACR;CACA,CAAA;AAEb"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Masonry.module.cjs","names":[],"sources":["../../../src/components/Masonry/Masonry.module.css"],"sourcesContent":[".wrapper {\n display: flex;\n align-items: flex-start;\n gap: var(--tempest-masonry-gap, var(--tempest-space-4));\n width: 100%;\n}\n\n.column {\n display: flex;\n flex: 1 1 0;\n flex-direction: column;\n gap: var(--tempest-masonry-gap, var(--tempest-space-4));\n min-width: 0;\n}\n\n.item {\n min-width: 0;\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/components/Masonry/Masonry.module.css
|
|
2
|
+
var e = "tempest_wrapper_xO0g4", t = "tempest_column_FX2W1", n = "tempest_item_m7Agj", r = {
|
|
3
|
+
wrapper: e,
|
|
4
|
+
column: t,
|
|
5
|
+
item: n
|
|
6
|
+
};
|
|
7
|
+
//#endregion
|
|
8
|
+
export { t as column, r as default, n as item, e as wrapper };
|
|
9
|
+
|
|
10
|
+
//# sourceMappingURL=Masonry.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Masonry.module.js","names":[],"sources":["../../../src/components/Masonry/Masonry.module.css"],"sourcesContent":[".wrapper {\n display: flex;\n align-items: flex-start;\n gap: var(--tempest-masonry-gap, var(--tempest-space-4));\n width: 100%;\n}\n\n.column {\n display: flex;\n flex: 1 1 0;\n flex-direction: column;\n gap: var(--tempest-masonry-gap, var(--tempest-space-4));\n min-width: 0;\n}\n\n.item {\n min-width: 0;\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(e,t){if(typeof t==`number`)return Math.max(1,Math.floor(t));let n=Object.keys(t).map(Number).filter(e=>Number.isFinite(e)).sort((e,t)=>e-t),r=1;for(let i of n)e>=i&&(r=t[i]);return Math.max(1,Math.floor(r))}function t(e,t){let n=Math.max(1,Math.floor(t)),r=Array.from({length:n},()=>[]),i=Array(n).fill(0);for(let t=0;t<e.length;t+=1){let a=0;for(let e=1;e<n;e+=1)i[e]<i[a]&&(a=e);r[a].push(t),i[a]+=Math.max(1,e[t]??1)}return r}exports.columnsFor=e,exports.distribute=t;
|
|
2
|
+
//# sourceMappingURL=masonry-layout.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"masonry-layout.cjs","names":[],"sources":["../../../src/components/Masonry/masonry-layout.ts"],"sourcesContent":["/**\n * Column count for a width, from a breakpoint map.\n *\n * The map is read as \"from this width up\", so `{ 0: 1, 640: 2, 1024: 3 }` means one\n * column on a phone and three on a desktop. Keys are sorted here rather than\n * trusted in insertion order — an object literal written out of order would\n * otherwise pick the wrong column count, silently.\n *\n * @param width - Container width in pixels.\n * @param columns - Breakpoint → column count, or a fixed number.\n * @returns At least 1, always.\n */\nexport function columnsFor(width: number, columns: number | Record<number, number>): number {\n if (typeof columns === \"number\") return Math.max(1, Math.floor(columns));\n const steps = Object.keys(columns)\n .map(Number)\n .filter((key) => Number.isFinite(key))\n .sort((a, b) => a - b);\n let count = 1;\n for (const step of steps) {\n if (width >= step) count = columns[step];\n }\n return Math.max(1, Math.floor(count));\n}\n\n/**\n * Deal items into columns, shortest column first.\n *\n * Round-robin (`index % columns`) is the obvious approach and produces ragged\n * columns the moment items differ in height — which is the only reason to reach\n * for a masonry layout at all. Feeding the shortest column keeps the bottom edge\n * as even as the content allows.\n *\n * Reading order is the cost, and it is why this is a layout for **independent**\n * cards: down a column rather than across the row. A list where item 2 must follow\n * item 1 wants a grid, not this.\n *\n * @param heights - Estimated or measured height per item, in the items' order.\n * @param columnCount - How many columns to fill.\n * @returns Item indexes per column.\n */\nexport function distribute(heights: readonly number[], columnCount: number): number[][] {\n const count = Math.max(1, Math.floor(columnCount));\n const columns: number[][] = Array.from({ length: count }, () => []);\n const totals = new Array<number>(count).fill(0);\n\n for (let index = 0; index < heights.length; index += 1) {\n let shortest = 0;\n for (let column = 1; column < count; column += 1) {\n if (totals[column] < totals[shortest]) shortest = column;\n }\n columns[shortest].push(index);\n totals[shortest] += Math.max(1, heights[index] ?? 1);\n }\n\n return columns;\n}\n"],"mappings":"AAYA,SAAgB,EAAW,EAAe,EAAkD,CACxF,GAAI,OAAO,GAAY,SAAU,OAAO,KAAK,IAAI,EAAG,KAAK,MAAM,CAAO,CAAC,EACvE,IAAM,EAAQ,OAAO,KAAK,CAAO,CAAC,CAC7B,IAAI,MAAM,CAAC,CACX,OAAQ,GAAQ,OAAO,SAAS,CAAG,CAAC,CAAC,CACrC,MAAM,EAAG,IAAM,EAAI,CAAC,EACrB,EAAQ,EACZ,IAAK,IAAM,KAAQ,EACX,GAAS,IAAM,EAAQ,EAAQ,IAEvC,OAAO,KAAK,IAAI,EAAG,KAAK,MAAM,CAAK,CAAC,CACxC,CAkBA,SAAgB,EAAW,EAA4B,EAAiC,CACpF,IAAM,EAAQ,KAAK,IAAI,EAAG,KAAK,MAAM,CAAW,CAAC,EAC3C,EAAsB,MAAM,KAAK,CAAE,OAAQ,CAAM,MAAS,CAAC,CAAC,EAC5D,EAAa,MAAc,CAAK,CAAC,CAAC,KAAK,CAAC,EAE9C,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAQ,OAAQ,GAAS,EAAG,CACpD,IAAI,EAAW,EACf,IAAK,IAAI,EAAS,EAAG,EAAS,EAAO,GAAU,EACvC,EAAO,GAAU,EAAO,KAAW,EAAW,GAEtD,EAAQ,EAAS,CAAC,KAAK,CAAK,EAC5B,EAAO,IAAa,KAAK,IAAI,EAAG,EAAQ,IAAU,CAAC,CACvD,CAEA,OAAO,CACX"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/components/Masonry/masonry-layout.ts
|
|
2
|
+
function e(e, t) {
|
|
3
|
+
if (typeof t == "number") return Math.max(1, Math.floor(t));
|
|
4
|
+
let n = Object.keys(t).map(Number).filter((e) => Number.isFinite(e)).sort((e, t) => e - t), r = 1;
|
|
5
|
+
for (let i of n) e >= i && (r = t[i]);
|
|
6
|
+
return Math.max(1, Math.floor(r));
|
|
7
|
+
}
|
|
8
|
+
function t(e, t) {
|
|
9
|
+
let n = Math.max(1, Math.floor(t)), r = Array.from({ length: n }, () => []), i = Array(n).fill(0);
|
|
10
|
+
for (let t = 0; t < e.length; t += 1) {
|
|
11
|
+
let a = 0;
|
|
12
|
+
for (let e = 1; e < n; e += 1) i[e] < i[a] && (a = e);
|
|
13
|
+
r[a].push(t), i[a] += Math.max(1, e[t] ?? 1);
|
|
14
|
+
}
|
|
15
|
+
return r;
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { e as columnsFor, t as distribute };
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=masonry-layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"masonry-layout.js","names":[],"sources":["../../../src/components/Masonry/masonry-layout.ts"],"sourcesContent":["/**\n * Column count for a width, from a breakpoint map.\n *\n * The map is read as \"from this width up\", so `{ 0: 1, 640: 2, 1024: 3 }` means one\n * column on a phone and three on a desktop. Keys are sorted here rather than\n * trusted in insertion order — an object literal written out of order would\n * otherwise pick the wrong column count, silently.\n *\n * @param width - Container width in pixels.\n * @param columns - Breakpoint → column count, or a fixed number.\n * @returns At least 1, always.\n */\nexport function columnsFor(width: number, columns: number | Record<number, number>): number {\n if (typeof columns === \"number\") return Math.max(1, Math.floor(columns));\n const steps = Object.keys(columns)\n .map(Number)\n .filter((key) => Number.isFinite(key))\n .sort((a, b) => a - b);\n let count = 1;\n for (const step of steps) {\n if (width >= step) count = columns[step];\n }\n return Math.max(1, Math.floor(count));\n}\n\n/**\n * Deal items into columns, shortest column first.\n *\n * Round-robin (`index % columns`) is the obvious approach and produces ragged\n * columns the moment items differ in height — which is the only reason to reach\n * for a masonry layout at all. Feeding the shortest column keeps the bottom edge\n * as even as the content allows.\n *\n * Reading order is the cost, and it is why this is a layout for **independent**\n * cards: down a column rather than across the row. A list where item 2 must follow\n * item 1 wants a grid, not this.\n *\n * @param heights - Estimated or measured height per item, in the items' order.\n * @param columnCount - How many columns to fill.\n * @returns Item indexes per column.\n */\nexport function distribute(heights: readonly number[], columnCount: number): number[][] {\n const count = Math.max(1, Math.floor(columnCount));\n const columns: number[][] = Array.from({ length: count }, () => []);\n const totals = new Array<number>(count).fill(0);\n\n for (let index = 0; index < heights.length; index += 1) {\n let shortest = 0;\n for (let column = 1; column < count; column += 1) {\n if (totals[column] < totals[shortest]) shortest = column;\n }\n columns[shortest].push(index);\n totals[shortest] += Math.max(1, heights[index] ?? 1);\n }\n\n return columns;\n}\n"],"mappings":";AAYA,SAAgB,EAAW,GAAe,GAAkD;CACxF,IAAI,OAAO,KAAY,UAAU,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,CAAO,CAAC;CACvE,IAAM,IAAQ,OAAO,KAAK,CAAO,CAAC,CAC7B,IAAI,MAAM,CAAC,CACX,QAAQ,MAAQ,OAAO,SAAS,CAAG,CAAC,CAAC,CACrC,MAAM,GAAG,MAAM,IAAI,CAAC,GACrB,IAAQ;CACZ,KAAK,IAAM,KAAQ,GACf,AAAI,KAAS,MAAM,IAAQ,EAAQ;CAEvC,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,CAAK,CAAC;AACxC;AAkBA,SAAgB,EAAW,GAA4B,GAAiC;CACpF,IAAM,IAAQ,KAAK,IAAI,GAAG,KAAK,MAAM,CAAW,CAAC,GAC3C,IAAsB,MAAM,KAAK,EAAE,QAAQ,EAAM,SAAS,CAAC,CAAC,GAC5D,IAAa,MAAc,CAAK,CAAC,CAAC,KAAK,CAAC;CAE9C,KAAK,IAAI,IAAQ,GAAG,IAAQ,EAAQ,QAAQ,KAAS,GAAG;EACpD,IAAI,IAAW;EACf,KAAK,IAAI,IAAS,GAAG,IAAS,GAAO,KAAU,GAC3C,AAAI,EAAO,KAAU,EAAO,OAAW,IAAW;EAGtD,AADA,EAAQ,EAAS,CAAC,KAAK,CAAK,GAC5B,EAAO,MAAa,KAAK,IAAI,GAAG,EAAQ,MAAU,CAAC;CACvD;CAEA,OAAO;AACX"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("../../utils/cn.cjs"),t=require("../Checkbox/Checkbox.cjs"),n=require("../VisuallyHidden/VisuallyHidden.cjs"),r=require("./transfer-state.cjs"),i=require("./Transfer.module.cjs");let a=require("react"),o=require("react/jsx-runtime");function s({items:s,value:c,onChange:l,sourceTitle:u,targetTitle:d,searchable:f,renderItem:p,locale:m=`pt-BR`,height:h=`16rem`,disabled:g=!1,className:_,...v}){let y=r.transferStrings(m),b=(0,a.useId)(),[x,S]=(0,a.useState)(new Set),[C,w]=(0,a.useState)({source:``,target:``}),[T,E]=(0,a.useState)(``),{source:D,target:O}=(0,a.useMemo)(()=>r.splitSides(s,c),[s,c]),k=f??s.length>8,A={source:k?r.filterItems(D,C.source):[...D],target:k?r.filterItems(O,C.target):[...O]},j=[{side:`source`,title:u??y.sourceTitle,items:D},{side:`target`,title:d??y.targetTitle,items:O}],M=(e,t)=>{if(g||e.length===0)return;let n=r.applyMove({value:c,moving:e,to:t,items:s}),i=t===`target`?n.length-c.length:c.length-n.length;if(l(n),S(t=>{let n=new Set(t);for(let t of e)n.delete(t);return n}),i>0){let e=t===`target`?y.targetTitle:y.sourceTitle;E(y.moved(i,String(e).toLowerCase()))}},N=e=>{S(t=>{let n=new Set(t);return n.has(e)?n.delete(e):n.add(e),n})},P=e=>r.movableIds(A[e]).filter(e=>x.has(e)),F=P(`source`),I=P(`target`);return(0,o.jsxs)(`div`,{className:e.cn(i.default.wrapper,_),...v,children:[j.map(e=>{let n=A[e.side],a=r.movableIds(n),s=a.filter(e=>x.has(e)),c=a.length>0&&s.length===a.length,l=`${b}-${e.side}-title`;return(0,o.jsxs)(`section`,{className:i.default.pane,"aria-labelledby":l,children:[(0,o.jsxs)(`div`,{className:i.default.paneHeader,children:[(0,o.jsxs)(`div`,{className:i.default.paneTitle,children:[(0,o.jsx)(t.Checkbox,{checked:c,indeterminate:s.length>0&&!c,disabled:g||a.length===0,"aria-label":typeof e.title==`string`?e.title:String(e.side),onChange:()=>S(e=>{let t=new Set(e);for(let e of a)c?t.delete(e):t.add(e);return t})}),(0,o.jsx)(`h3`,{className:i.default.title,id:l,children:e.title})]}),(0,o.jsx)(`span`,{className:i.default.count,children:y.selected(s.length,e.items.length)})]}),k&&(0,o.jsx)(`input`,{type:`search`,className:i.default.search,value:C[e.side],placeholder:y.search,"aria-label":`${y.search}: ${typeof e.title==`string`?e.title:e.side}`,onChange:t=>w(n=>({...n,[e.side]:t.target.value}))}),(0,o.jsx)(`ul`,{className:i.default.list,style:{height:h},children:n.length===0?(0,o.jsx)(`li`,{className:i.default.empty,children:C[e.side]?y.noMatches:y.empty}):n.map(n=>(0,o.jsx)(`li`,{className:i.default.row,children:(0,o.jsx)(t.Checkbox,{checked:x.has(n.id),disabled:g||n.disabled,label:p?p(n,e.side):n.label,onChange:()=>N(n.id),onDoubleClick:()=>M([n.id],e.side===`source`?`target`:`source`)})},n.id))})]},e.side)}),(0,o.jsxs)(`div`,{className:i.default.controls,children:[(0,o.jsx)(`button`,{type:`button`,className:i.default.control,disabled:g||r.movableIds(A.source).length===0,"aria-label":y.allToTarget,title:y.allToTarget,onClick:()=>M(r.movableIds(A.source),`target`),children:`»`}),(0,o.jsx)(`button`,{type:`button`,className:i.default.control,disabled:g||F.length===0,"aria-label":y.toTarget,title:y.toTarget,onClick:()=>M(F,`target`),children:`›`}),(0,o.jsx)(`button`,{type:`button`,className:i.default.control,disabled:g||I.length===0,"aria-label":y.toSource,title:y.toSource,onClick:()=>M(I,`source`),children:`‹`}),(0,o.jsx)(`button`,{type:`button`,className:i.default.control,disabled:g||r.movableIds(A.target).length===0,"aria-label":y.allToSource,title:y.allToSource,onClick:()=>M(r.movableIds(A.target),`source`),children:`«`})]}),(0,o.jsx)(n.VisuallyHidden,{"aria-live":`polite`,role:`status`,children:T})]})}exports.Transfer=s;
|
|
2
|
+
//# sourceMappingURL=Transfer.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Transfer.cjs","names":[],"sources":["../../../src/components/Transfer/Transfer.tsx"],"sourcesContent":["import { useId, useMemo, useState, type HTMLAttributes, type ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { Checkbox } from \"../Checkbox\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport {\n applyMove,\n filterItems,\n movableIds,\n splitSides,\n transferStrings,\n type TransferItem,\n type TransferSide,\n} from \"./transfer-state\";\nimport styles from \"./Transfer.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\" | \"onChange\" | \"defaultValue\";\n\nexport interface TransferProps extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** The whole catalogue. Both panes are derived from it. */\n items: readonly TransferItem[];\n /** Ids on the target side. Controlled. */\n value: readonly string[];\n /** Next value, always in catalogue order. */\n onChange: (value: string[]) => void;\n /** Heading of the left pane. Default `\"Disponíveis\"`. */\n sourceTitle?: ReactNode;\n /** Heading of the right pane. Default `\"Selecionados\"`. */\n targetTitle?: ReactNode;\n /** Show a search box above each pane. Default `true` past 8 items. */\n searchable?: boolean;\n /** Custom row body. Gets the item and which side it is on. */\n renderItem?: (item: TransferItem, side: TransferSide) => ReactNode;\n /** Locale for labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Height of each pane's scroll area, any CSS length. Default `\"16rem\"`. */\n height?: string;\n /** Block every move. */\n disabled?: boolean;\n}\n\n/**\n * Two panes and a set of move controls: pick a subset out of a catalogue.\n *\n * Controlled on **ids of the target side only**. The panes are derived on every\n * render, never stored, because two stored lists drift the moment the catalogue\n * changes under them — a permission removed upstream lingers in whichever pane\n * held it, and an id in both panes is a bug nobody can see.\n *\n * @example\n * const [roles, setRoles] = useState<string[]>([]);\n *\n * <Transfer\n * items={allRoles}\n * value={roles}\n * onChange={setRoles}\n * sourceTitle=\"Papéis disponíveis\"\n * targetTitle=\"Papéis do usuário\"\n * />\n */\nexport function Transfer({\n items,\n value,\n onChange,\n sourceTitle,\n targetTitle,\n searchable,\n renderItem,\n locale = \"pt-BR\",\n height = \"16rem\",\n disabled = false,\n className,\n ...rest\n}: TransferProps) {\n const strings = transferStrings(locale);\n const baseId = useId();\n const [checked, setChecked] = useState<Set<string>>(new Set());\n const [queries, setQueries] = useState<{ source: string; target: string }>({\n source: \"\",\n target: \"\",\n });\n const [announcement, setAnnouncement] = useState(\"\");\n\n const { source, target } = useMemo(() => splitSides(items, value), [items, value]);\n const withSearch = searchable ?? items.length > 8;\n\n /**\n * What each pane is showing right now — the filter included.\n *\n * Every control acts on this, not on the pane's full contents: filtering to\n * \"sao\" and pressing \"move all\" has to move what you are looking at. Moving\n * rows the filter hid is the kind of surprise that makes people stop trusting\n * the bulk button.\n */\n const visible: Record<TransferSide, TransferItem[]> = {\n source: withSearch ? filterItems(source, queries.source) : [...source],\n target: withSearch ? filterItems(target, queries.target) : [...target],\n };\n\n const panes: Array<{ side: TransferSide; title: ReactNode; items: TransferItem[] }> = [\n { side: \"source\", title: sourceTitle ?? strings.sourceTitle, items: source },\n { side: \"target\", title: targetTitle ?? strings.targetTitle, items: target },\n ];\n\n /**\n * Move ids and drop them from the checked set.\n *\n * Clearing the checks is the point: after a move the rows are on the other\n * side, and leaving them checked means the next click on the opposite button\n * sends them straight back — which reads as the component undoing itself.\n */\n const move = (moving: readonly string[], to: TransferSide): void => {\n if (disabled || moving.length === 0) return;\n const next = applyMove({ value, moving, to, items });\n const changed = to === \"target\" ? next.length - value.length : value.length - next.length;\n onChange(next);\n setChecked((current) => {\n const remaining = new Set(current);\n for (const id of moving) remaining.delete(id);\n return remaining;\n });\n if (changed > 0) {\n const sideName = to === \"target\" ? strings.targetTitle : strings.sourceTitle;\n setAnnouncement(strings.moved(changed, String(sideName).toLowerCase()));\n }\n };\n\n const toggle = (id: string): void => {\n setChecked((current) => {\n const next = new Set(current);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n return next;\n });\n };\n\n /** Ids checked **and visible** on one side — what the move buttons act on. */\n const checkedOn = (side: TransferSide): string[] =>\n movableIds(visible[side]).filter((id) => checked.has(id));\n\n const checkedSource = checkedOn(\"source\");\n const checkedTarget = checkedOn(\"target\");\n\n return (\n <div className={cn(styles.wrapper, className)} {...rest}>\n {panes.map((pane) => {\n const shown = visible[pane.side];\n const movable = movableIds(shown);\n const checkedHere = movable.filter((id) => checked.has(id));\n const allChecked = movable.length > 0 && checkedHere.length === movable.length;\n const titleId = `${baseId}-${pane.side}-title`;\n\n return (\n <section key={pane.side} className={styles.pane} aria-labelledby={titleId}>\n <div className={styles.paneHeader}>\n <div className={styles.paneTitle}>\n <Checkbox\n checked={allChecked}\n indeterminate={checkedHere.length > 0 && !allChecked}\n disabled={disabled || movable.length === 0}\n aria-label={\n typeof pane.title === \"string\"\n ? pane.title\n : String(pane.side)\n }\n onChange={() =>\n setChecked((current) => {\n const next = new Set(current);\n for (const id of movable) {\n if (allChecked) next.delete(id);\n else next.add(id);\n }\n return next;\n })\n }\n />\n <h3 className={styles.title} id={titleId}>\n {pane.title}\n </h3>\n </div>\n <span className={styles.count}>\n {strings.selected(checkedHere.length, pane.items.length)}\n </span>\n </div>\n\n {withSearch && (\n <input\n type=\"search\"\n className={styles.search}\n value={queries[pane.side]}\n placeholder={strings.search}\n aria-label={`${strings.search}: ${\n typeof pane.title === \"string\" ? pane.title : pane.side\n }`}\n onChange={(event) =>\n setQueries((current) => ({\n ...current,\n [pane.side]: event.target.value,\n }))\n }\n />\n )}\n\n <ul className={styles.list} style={{ height }}>\n {shown.length === 0 ? (\n <li className={styles.empty}>\n {queries[pane.side] ? strings.noMatches : strings.empty}\n </li>\n ) : (\n shown.map((item) => (\n <li key={item.id} className={styles.row}>\n <Checkbox\n checked={checked.has(item.id)}\n disabled={disabled || item.disabled}\n label={\n renderItem\n ? renderItem(item, pane.side)\n : item.label\n }\n onChange={() => toggle(item.id)}\n onDoubleClick={() =>\n move(\n [item.id],\n pane.side === \"source\" ? \"target\" : \"source\",\n )\n }\n />\n </li>\n ))\n )}\n </ul>\n </section>\n );\n })}\n\n <div className={styles.controls}>\n <button\n type=\"button\"\n className={styles.control}\n disabled={disabled || movableIds(visible.source).length === 0}\n aria-label={strings.allToTarget}\n title={strings.allToTarget}\n onClick={() => move(movableIds(visible.source), \"target\")}\n >\n »\n </button>\n <button\n type=\"button\"\n className={styles.control}\n disabled={disabled || checkedSource.length === 0}\n aria-label={strings.toTarget}\n title={strings.toTarget}\n onClick={() => move(checkedSource, \"target\")}\n >\n ›\n </button>\n <button\n type=\"button\"\n className={styles.control}\n disabled={disabled || checkedTarget.length === 0}\n aria-label={strings.toSource}\n title={strings.toSource}\n onClick={() => move(checkedTarget, \"source\")}\n >\n ‹\n </button>\n <button\n type=\"button\"\n className={styles.control}\n disabled={disabled || movableIds(visible.target).length === 0}\n aria-label={strings.allToSource}\n title={strings.allToSource}\n onClick={() => move(movableIds(visible.target), \"source\")}\n >\n «\n </button>\n </div>\n\n <VisuallyHidden aria-live=\"polite\" role=\"status\">\n {announcement}\n </VisuallyHidden>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { cn as e } from "../../utils/cn.js";
|
|
2
|
+
import { Checkbox as t } from "../Checkbox/Checkbox.js";
|
|
3
|
+
import { VisuallyHidden as n } from "../VisuallyHidden/VisuallyHidden.js";
|
|
4
|
+
import { applyMove as r, filterItems as i, movableIds as a, splitSides as o, transferStrings as s } from "./transfer-state.js";
|
|
5
|
+
import c from "./Transfer.module.js";
|
|
6
|
+
import { useId as l, useMemo as u, useState as d } from "react";
|
|
7
|
+
import { jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
8
|
+
//#region src/components/Transfer/Transfer.tsx
|
|
9
|
+
function m({ items: m, value: h, onChange: g, sourceTitle: _, targetTitle: v, searchable: y, renderItem: b, locale: x = "pt-BR", height: S = "16rem", disabled: C = !1, className: w, ...T }) {
|
|
10
|
+
let E = s(x), D = l(), [O, k] = d(/* @__PURE__ */ new Set()), [A, j] = d({
|
|
11
|
+
source: "",
|
|
12
|
+
target: ""
|
|
13
|
+
}), [M, N] = d(""), { source: P, target: F } = u(() => o(m, h), [m, h]), I = y ?? m.length > 8, L = {
|
|
14
|
+
source: I ? i(P, A.source) : [...P],
|
|
15
|
+
target: I ? i(F, A.target) : [...F]
|
|
16
|
+
}, R = [{
|
|
17
|
+
side: "source",
|
|
18
|
+
title: _ ?? E.sourceTitle,
|
|
19
|
+
items: P
|
|
20
|
+
}, {
|
|
21
|
+
side: "target",
|
|
22
|
+
title: v ?? E.targetTitle,
|
|
23
|
+
items: F
|
|
24
|
+
}], z = (e, t) => {
|
|
25
|
+
if (C || e.length === 0) return;
|
|
26
|
+
let n = r({
|
|
27
|
+
value: h,
|
|
28
|
+
moving: e,
|
|
29
|
+
to: t,
|
|
30
|
+
items: m
|
|
31
|
+
}), i = t === "target" ? n.length - h.length : h.length - n.length;
|
|
32
|
+
if (g(n), k((t) => {
|
|
33
|
+
let n = new Set(t);
|
|
34
|
+
for (let t of e) n.delete(t);
|
|
35
|
+
return n;
|
|
36
|
+
}), i > 0) {
|
|
37
|
+
let e = t === "target" ? E.targetTitle : E.sourceTitle;
|
|
38
|
+
N(E.moved(i, String(e).toLowerCase()));
|
|
39
|
+
}
|
|
40
|
+
}, B = (e) => {
|
|
41
|
+
k((t) => {
|
|
42
|
+
let n = new Set(t);
|
|
43
|
+
return n.has(e) ? n.delete(e) : n.add(e), n;
|
|
44
|
+
});
|
|
45
|
+
}, V = (e) => a(L[e]).filter((e) => O.has(e)), H = V("source"), U = V("target");
|
|
46
|
+
return /* @__PURE__ */ p("div", {
|
|
47
|
+
className: e(c.wrapper, w),
|
|
48
|
+
...T,
|
|
49
|
+
children: [
|
|
50
|
+
R.map((e) => {
|
|
51
|
+
let n = L[e.side], r = a(n), i = r.filter((e) => O.has(e)), o = r.length > 0 && i.length === r.length, s = `${D}-${e.side}-title`;
|
|
52
|
+
return /* @__PURE__ */ p("section", {
|
|
53
|
+
className: c.pane,
|
|
54
|
+
"aria-labelledby": s,
|
|
55
|
+
children: [
|
|
56
|
+
/* @__PURE__ */ p("div", {
|
|
57
|
+
className: c.paneHeader,
|
|
58
|
+
children: [/* @__PURE__ */ p("div", {
|
|
59
|
+
className: c.paneTitle,
|
|
60
|
+
children: [/* @__PURE__ */ f(t, {
|
|
61
|
+
checked: o,
|
|
62
|
+
indeterminate: i.length > 0 && !o,
|
|
63
|
+
disabled: C || r.length === 0,
|
|
64
|
+
"aria-label": typeof e.title == "string" ? e.title : String(e.side),
|
|
65
|
+
onChange: () => k((e) => {
|
|
66
|
+
let t = new Set(e);
|
|
67
|
+
for (let e of r) o ? t.delete(e) : t.add(e);
|
|
68
|
+
return t;
|
|
69
|
+
})
|
|
70
|
+
}), /* @__PURE__ */ f("h3", {
|
|
71
|
+
className: c.title,
|
|
72
|
+
id: s,
|
|
73
|
+
children: e.title
|
|
74
|
+
})]
|
|
75
|
+
}), /* @__PURE__ */ f("span", {
|
|
76
|
+
className: c.count,
|
|
77
|
+
children: E.selected(i.length, e.items.length)
|
|
78
|
+
})]
|
|
79
|
+
}),
|
|
80
|
+
I && /* @__PURE__ */ f("input", {
|
|
81
|
+
type: "search",
|
|
82
|
+
className: c.search,
|
|
83
|
+
value: A[e.side],
|
|
84
|
+
placeholder: E.search,
|
|
85
|
+
"aria-label": `${E.search}: ${typeof e.title == "string" ? e.title : e.side}`,
|
|
86
|
+
onChange: (t) => j((n) => ({
|
|
87
|
+
...n,
|
|
88
|
+
[e.side]: t.target.value
|
|
89
|
+
}))
|
|
90
|
+
}),
|
|
91
|
+
/* @__PURE__ */ f("ul", {
|
|
92
|
+
className: c.list,
|
|
93
|
+
style: { height: S },
|
|
94
|
+
children: n.length === 0 ? /* @__PURE__ */ f("li", {
|
|
95
|
+
className: c.empty,
|
|
96
|
+
children: A[e.side] ? E.noMatches : E.empty
|
|
97
|
+
}) : n.map((n) => /* @__PURE__ */ f("li", {
|
|
98
|
+
className: c.row,
|
|
99
|
+
children: /* @__PURE__ */ f(t, {
|
|
100
|
+
checked: O.has(n.id),
|
|
101
|
+
disabled: C || n.disabled,
|
|
102
|
+
label: b ? b(n, e.side) : n.label,
|
|
103
|
+
onChange: () => B(n.id),
|
|
104
|
+
onDoubleClick: () => z([n.id], e.side === "source" ? "target" : "source")
|
|
105
|
+
})
|
|
106
|
+
}, n.id))
|
|
107
|
+
})
|
|
108
|
+
]
|
|
109
|
+
}, e.side);
|
|
110
|
+
}),
|
|
111
|
+
/* @__PURE__ */ p("div", {
|
|
112
|
+
className: c.controls,
|
|
113
|
+
children: [
|
|
114
|
+
/* @__PURE__ */ f("button", {
|
|
115
|
+
type: "button",
|
|
116
|
+
className: c.control,
|
|
117
|
+
disabled: C || a(L.source).length === 0,
|
|
118
|
+
"aria-label": E.allToTarget,
|
|
119
|
+
title: E.allToTarget,
|
|
120
|
+
onClick: () => z(a(L.source), "target"),
|
|
121
|
+
children: "»"
|
|
122
|
+
}),
|
|
123
|
+
/* @__PURE__ */ f("button", {
|
|
124
|
+
type: "button",
|
|
125
|
+
className: c.control,
|
|
126
|
+
disabled: C || H.length === 0,
|
|
127
|
+
"aria-label": E.toTarget,
|
|
128
|
+
title: E.toTarget,
|
|
129
|
+
onClick: () => z(H, "target"),
|
|
130
|
+
children: "›"
|
|
131
|
+
}),
|
|
132
|
+
/* @__PURE__ */ f("button", {
|
|
133
|
+
type: "button",
|
|
134
|
+
className: c.control,
|
|
135
|
+
disabled: C || U.length === 0,
|
|
136
|
+
"aria-label": E.toSource,
|
|
137
|
+
title: E.toSource,
|
|
138
|
+
onClick: () => z(U, "source"),
|
|
139
|
+
children: "‹"
|
|
140
|
+
}),
|
|
141
|
+
/* @__PURE__ */ f("button", {
|
|
142
|
+
type: "button",
|
|
143
|
+
className: c.control,
|
|
144
|
+
disabled: C || a(L.target).length === 0,
|
|
145
|
+
"aria-label": E.allToSource,
|
|
146
|
+
title: E.allToSource,
|
|
147
|
+
onClick: () => z(a(L.target), "source"),
|
|
148
|
+
children: "«"
|
|
149
|
+
})
|
|
150
|
+
]
|
|
151
|
+
}),
|
|
152
|
+
/* @__PURE__ */ f(n, {
|
|
153
|
+
"aria-live": "polite",
|
|
154
|
+
role: "status",
|
|
155
|
+
children: M
|
|
156
|
+
})
|
|
157
|
+
]
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
//#endregion
|
|
161
|
+
export { m as Transfer };
|
|
162
|
+
|
|
163
|
+
//# sourceMappingURL=Transfer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Transfer.js","names":[],"sources":["../../../src/components/Transfer/Transfer.tsx"],"sourcesContent":["import { useId, useMemo, useState, type HTMLAttributes, type ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { Checkbox } from \"../Checkbox\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport {\n applyMove,\n filterItems,\n movableIds,\n splitSides,\n transferStrings,\n type TransferItem,\n type TransferSide,\n} from \"./transfer-state\";\nimport styles from \"./Transfer.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\" | \"onChange\" | \"defaultValue\";\n\nexport interface TransferProps extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** The whole catalogue. Both panes are derived from it. */\n items: readonly TransferItem[];\n /** Ids on the target side. Controlled. */\n value: readonly string[];\n /** Next value, always in catalogue order. */\n onChange: (value: string[]) => void;\n /** Heading of the left pane. Default `\"Disponíveis\"`. */\n sourceTitle?: ReactNode;\n /** Heading of the right pane. Default `\"Selecionados\"`. */\n targetTitle?: ReactNode;\n /** Show a search box above each pane. Default `true` past 8 items. */\n searchable?: boolean;\n /** Custom row body. Gets the item and which side it is on. */\n renderItem?: (item: TransferItem, side: TransferSide) => ReactNode;\n /** Locale for labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Height of each pane's scroll area, any CSS length. Default `\"16rem\"`. */\n height?: string;\n /** Block every move. */\n disabled?: boolean;\n}\n\n/**\n * Two panes and a set of move controls: pick a subset out of a catalogue.\n *\n * Controlled on **ids of the target side only**. The panes are derived on every\n * render, never stored, because two stored lists drift the moment the catalogue\n * changes under them — a permission removed upstream lingers in whichever pane\n * held it, and an id in both panes is a bug nobody can see.\n *\n * @example\n * const [roles, setRoles] = useState<string[]>([]);\n *\n * <Transfer\n * items={allRoles}\n * value={roles}\n * onChange={setRoles}\n * sourceTitle=\"Papéis disponíveis\"\n * targetTitle=\"Papéis do usuário\"\n * />\n */\nexport function Transfer({\n items,\n value,\n onChange,\n sourceTitle,\n targetTitle,\n searchable,\n renderItem,\n locale = \"pt-BR\",\n height = \"16rem\",\n disabled = false,\n className,\n ...rest\n}: TransferProps) {\n const strings = transferStrings(locale);\n const baseId = useId();\n const [checked, setChecked] = useState<Set<string>>(new Set());\n const [queries, setQueries] = useState<{ source: string; target: string }>({\n source: \"\",\n target: \"\",\n });\n const [announcement, setAnnouncement] = useState(\"\");\n\n const { source, target } = useMemo(() => splitSides(items, value), [items, value]);\n const withSearch = searchable ?? items.length > 8;\n\n /**\n * What each pane is showing right now — the filter included.\n *\n * Every control acts on this, not on the pane's full contents: filtering to\n * \"sao\" and pressing \"move all\" has to move what you are looking at. Moving\n * rows the filter hid is the kind of surprise that makes people stop trusting\n * the bulk button.\n */\n const visible: Record<TransferSide, TransferItem[]> = {\n source: withSearch ? filterItems(source, queries.source) : [...source],\n target: withSearch ? filterItems(target, queries.target) : [...target],\n };\n\n const panes: Array<{ side: TransferSide; title: ReactNode; items: TransferItem[] }> = [\n { side: \"source\", title: sourceTitle ?? strings.sourceTitle, items: source },\n { side: \"target\", title: targetTitle ?? strings.targetTitle, items: target },\n ];\n\n /**\n * Move ids and drop them from the checked set.\n *\n * Clearing the checks is the point: after a move the rows are on the other\n * side, and leaving them checked means the next click on the opposite button\n * sends them straight back — which reads as the component undoing itself.\n */\n const move = (moving: readonly string[], to: TransferSide): void => {\n if (disabled || moving.length === 0) return;\n const next = applyMove({ value, moving, to, items });\n const changed = to === \"target\" ? next.length - value.length : value.length - next.length;\n onChange(next);\n setChecked((current) => {\n const remaining = new Set(current);\n for (const id of moving) remaining.delete(id);\n return remaining;\n });\n if (changed > 0) {\n const sideName = to === \"target\" ? strings.targetTitle : strings.sourceTitle;\n setAnnouncement(strings.moved(changed, String(sideName).toLowerCase()));\n }\n };\n\n const toggle = (id: string): void => {\n setChecked((current) => {\n const next = new Set(current);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n return next;\n });\n };\n\n /** Ids checked **and visible** on one side — what the move buttons act on. */\n const checkedOn = (side: TransferSide): string[] =>\n movableIds(visible[side]).filter((id) => checked.has(id));\n\n const checkedSource = checkedOn(\"source\");\n const checkedTarget = checkedOn(\"target\");\n\n return (\n <div className={cn(styles.wrapper, className)} {...rest}>\n {panes.map((pane) => {\n const shown = visible[pane.side];\n const movable = movableIds(shown);\n const checkedHere = movable.filter((id) => checked.has(id));\n const allChecked = movable.length > 0 && checkedHere.length === movable.length;\n const titleId = `${baseId}-${pane.side}-title`;\n\n return (\n <section key={pane.side} className={styles.pane} aria-labelledby={titleId}>\n <div className={styles.paneHeader}>\n <div className={styles.paneTitle}>\n <Checkbox\n checked={allChecked}\n indeterminate={checkedHere.length > 0 && !allChecked}\n disabled={disabled || movable.length === 0}\n aria-label={\n typeof pane.title === \"string\"\n ? pane.title\n : String(pane.side)\n }\n onChange={() =>\n setChecked((current) => {\n const next = new Set(current);\n for (const id of movable) {\n if (allChecked) next.delete(id);\n else next.add(id);\n }\n return next;\n })\n }\n />\n <h3 className={styles.title} id={titleId}>\n {pane.title}\n </h3>\n </div>\n <span className={styles.count}>\n {strings.selected(checkedHere.length, pane.items.length)}\n </span>\n </div>\n\n {withSearch && (\n <input\n type=\"search\"\n className={styles.search}\n value={queries[pane.side]}\n placeholder={strings.search}\n aria-label={`${strings.search}: ${\n typeof pane.title === \"string\" ? pane.title : pane.side\n }`}\n onChange={(event) =>\n setQueries((current) => ({\n ...current,\n [pane.side]: event.target.value,\n }))\n }\n />\n )}\n\n <ul className={styles.list} style={{ height }}>\n {shown.length === 0 ? (\n <li className={styles.empty}>\n {queries[pane.side] ? strings.noMatches : strings.empty}\n </li>\n ) : (\n shown.map((item) => (\n <li key={item.id} className={styles.row}>\n <Checkbox\n checked={checked.has(item.id)}\n disabled={disabled || item.disabled}\n label={\n renderItem\n ? renderItem(item, pane.side)\n : item.label\n }\n onChange={() => toggle(item.id)}\n onDoubleClick={() =>\n move(\n [item.id],\n pane.side === \"source\" ? \"target\" : \"source\",\n )\n }\n />\n </li>\n ))\n )}\n </ul>\n </section>\n );\n })}\n\n <div className={styles.controls}>\n <button\n type=\"button\"\n className={styles.control}\n disabled={disabled || movableIds(visible.source).length === 0}\n aria-label={strings.allToTarget}\n title={strings.allToTarget}\n onClick={() => move(movableIds(visible.source), \"target\")}\n >\n »\n </button>\n <button\n type=\"button\"\n className={styles.control}\n disabled={disabled || checkedSource.length === 0}\n aria-label={strings.toTarget}\n title={strings.toTarget}\n onClick={() => move(checkedSource, \"target\")}\n >\n ›\n </button>\n <button\n type=\"button\"\n className={styles.control}\n disabled={disabled || checkedTarget.length === 0}\n aria-label={strings.toSource}\n title={strings.toSource}\n onClick={() => move(checkedTarget, \"source\")}\n >\n ‹\n </button>\n <button\n type=\"button\"\n className={styles.control}\n disabled={disabled || movableIds(visible.target).length === 0}\n aria-label={strings.allToSource}\n title={strings.allToSource}\n onClick={() => move(movableIds(visible.target), \"source\")}\n >\n «\n </button>\n </div>\n\n <VisuallyHidden aria-live=\"polite\" role=\"status\">\n {announcement}\n </VisuallyHidden>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var e=`tempest_wrapper_v0MUQ`,t=`tempest_controls_Khwwy`,n=`tempest_pane_EZRbR`,r=`tempest_paneHeader_JymjN`,i=`tempest_paneTitle_FS-D2`,a=`tempest_title_IhvAM`,o=`tempest_count_2NX52`,s=`tempest_search_OPJ2A`,c=`tempest_list_LrZlc`,l=`tempest_row_uZH5v`,u=`tempest_empty_sM4jo`,d=`tempest_control_-y936`,f={wrapper:e,controls:t,pane:n,paneHeader:r,paneTitle:i,title:a,count:o,search:s,list:c,row:l,empty:u,control:d};exports.control=d,exports.controls=t,exports.count=o,exports.default=f,exports.empty=u,exports.list=c,exports.pane=n,exports.paneHeader=r,exports.paneTitle=i,exports.row=l,exports.search=s,exports.title=a,exports.wrapper=e;
|
|
2
|
+
//# sourceMappingURL=Transfer.module.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Transfer.module.cjs","names":[],"sources":["../../../src/components/Transfer/Transfer.module.css"],"sourcesContent":[".wrapper {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);\n gap: var(--tempest-space-3);\n align-items: stretch;\n font-family: var(--tempest-font-sans);\n}\n\n/*\n * The move controls sit between the panes in the grid, but come **last** in the\n * DOM: a keyboard reaching the buttons before it has seen what they act on has to\n * go back, and a screen reader reads \"move checked to the right\" with no idea what\n * is checked. Order 2 puts them back in the middle visually.\n */\n.controls {\n display: flex;\n flex-direction: column;\n justify-content: center;\n gap: var(--tempest-space-2);\n order: 2;\n}\n\n.pane {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-2);\n min-width: 0;\n padding: var(--tempest-space-3);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-lg);\n background-color: var(--tempest-bg);\n}\n\n.pane:first-of-type {\n order: 1;\n}\n\n.pane:last-of-type {\n order: 3;\n}\n\n.paneHeader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--tempest-space-2);\n min-width: 0;\n}\n\n.paneTitle {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-2);\n min-width: 0;\n}\n\n.title {\n margin: 0;\n overflow: hidden;\n color: var(--tempest-text);\n font-size: var(--tempest-text-sm);\n font-weight: var(--tempest-weight-semibold);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.count {\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-xs);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.search {\n width: 100%;\n min-width: 0;\n padding: var(--tempest-space-2) var(--tempest-space-3);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n background-color: var(--tempest-bg);\n color: inherit;\n font: inherit;\n font-size: var(--tempest-text-sm);\n}\n\n.search:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: -1px;\n}\n\n.list {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-1);\n margin: 0;\n padding: 0;\n overflow-y: auto;\n overscroll-behavior: contain;\n list-style: none;\n}\n\n.row {\n padding: var(--tempest-space-1) var(--tempest-space-2);\n border-radius: var(--tempest-radius-sm);\n font-size: var(--tempest-text-sm);\n}\n\n.row:hover {\n background-color: var(--tempest-surface);\n}\n\n.empty {\n padding: var(--tempest-space-4) var(--tempest-space-2);\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-sm);\n text-align: center;\n}\n\n.control {\n display: flex;\n align-items: center;\n justify-content: center;\n width: var(--tempest-control-height-sm);\n height: var(--tempest-control-height-sm);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n background-color: var(--tempest-bg);\n color: var(--tempest-text);\n font-size: var(--tempest-text-base);\n line-height: 1;\n cursor: pointer;\n}\n\n.control:hover:not(:disabled) {\n border-color: var(--tempest-primary);\n color: var(--tempest-primary);\n}\n\n.control:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: 1px;\n}\n\n.control:disabled {\n color: var(--tempest-text-subtle);\n cursor: not-allowed;\n}\n\n/*\n * Below the md breakpoint the panes stack and the controls turn horizontal: two\n * 16rem lists side by side on a phone leave each about 150px wide, where a role\n * name wraps to three lines.\n */\n@media (max-width: 640px) {\n .wrapper {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .controls {\n flex-direction: row;\n justify-content: center;\n }\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/components/Transfer/Transfer.module.css
|
|
2
|
+
var e = "tempest_wrapper_v0MUQ", t = "tempest_controls_Khwwy", n = "tempest_pane_EZRbR", r = "tempest_paneHeader_JymjN", i = "tempest_paneTitle_FS-D2", a = "tempest_title_IhvAM", o = "tempest_count_2NX52", s = "tempest_search_OPJ2A", c = "tempest_list_LrZlc", l = "tempest_row_uZH5v", u = "tempest_empty_sM4jo", d = "tempest_control_-y936", f = {
|
|
3
|
+
wrapper: e,
|
|
4
|
+
controls: t,
|
|
5
|
+
pane: n,
|
|
6
|
+
paneHeader: r,
|
|
7
|
+
paneTitle: i,
|
|
8
|
+
title: a,
|
|
9
|
+
count: o,
|
|
10
|
+
search: s,
|
|
11
|
+
list: c,
|
|
12
|
+
row: l,
|
|
13
|
+
empty: u,
|
|
14
|
+
control: d
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
export { d as control, t as controls, o as count, f as default, u as empty, c as list, n as pane, r as paneHeader, i as paneTitle, l as row, s as search, a as title, e as wrapper };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=Transfer.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Transfer.module.js","names":[],"sources":["../../../src/components/Transfer/Transfer.module.css"],"sourcesContent":[".wrapper {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);\n gap: var(--tempest-space-3);\n align-items: stretch;\n font-family: var(--tempest-font-sans);\n}\n\n/*\n * The move controls sit between the panes in the grid, but come **last** in the\n * DOM: a keyboard reaching the buttons before it has seen what they act on has to\n * go back, and a screen reader reads \"move checked to the right\" with no idea what\n * is checked. Order 2 puts them back in the middle visually.\n */\n.controls {\n display: flex;\n flex-direction: column;\n justify-content: center;\n gap: var(--tempest-space-2);\n order: 2;\n}\n\n.pane {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-2);\n min-width: 0;\n padding: var(--tempest-space-3);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-lg);\n background-color: var(--tempest-bg);\n}\n\n.pane:first-of-type {\n order: 1;\n}\n\n.pane:last-of-type {\n order: 3;\n}\n\n.paneHeader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--tempest-space-2);\n min-width: 0;\n}\n\n.paneTitle {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-2);\n min-width: 0;\n}\n\n.title {\n margin: 0;\n overflow: hidden;\n color: var(--tempest-text);\n font-size: var(--tempest-text-sm);\n font-weight: var(--tempest-weight-semibold);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.count {\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-xs);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.search {\n width: 100%;\n min-width: 0;\n padding: var(--tempest-space-2) var(--tempest-space-3);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n background-color: var(--tempest-bg);\n color: inherit;\n font: inherit;\n font-size: var(--tempest-text-sm);\n}\n\n.search:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: -1px;\n}\n\n.list {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-1);\n margin: 0;\n padding: 0;\n overflow-y: auto;\n overscroll-behavior: contain;\n list-style: none;\n}\n\n.row {\n padding: var(--tempest-space-1) var(--tempest-space-2);\n border-radius: var(--tempest-radius-sm);\n font-size: var(--tempest-text-sm);\n}\n\n.row:hover {\n background-color: var(--tempest-surface);\n}\n\n.empty {\n padding: var(--tempest-space-4) var(--tempest-space-2);\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-sm);\n text-align: center;\n}\n\n.control {\n display: flex;\n align-items: center;\n justify-content: center;\n width: var(--tempest-control-height-sm);\n height: var(--tempest-control-height-sm);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n background-color: var(--tempest-bg);\n color: var(--tempest-text);\n font-size: var(--tempest-text-base);\n line-height: 1;\n cursor: pointer;\n}\n\n.control:hover:not(:disabled) {\n border-color: var(--tempest-primary);\n color: var(--tempest-primary);\n}\n\n.control:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: 1px;\n}\n\n.control:disabled {\n color: var(--tempest-text-subtle);\n cursor: not-allowed;\n}\n\n/*\n * Below the md breakpoint the panes stack and the controls turn horizontal: two\n * 16rem lists side by side on a phone leave each about 150px wide, where a role\n * name wraps to three lines.\n */\n@media (max-width: 640px) {\n .wrapper {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .controls {\n flex-direction: row;\n justify-content: center;\n }\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(e,t){let n=new Set(t),r=[],i=[];for(let t of e)(n.has(t.id)?i:r).push(t);return{source:r,target:i}}function t(e){return e.searchText===void 0?typeof e.label==`string`?e.label:``:e.searchText}function n(e,n){let i=r(n);return i?e.filter(e=>r(t(e)).includes(i)):[...e]}function r(e){return e.normalize(`NFD`).replace(/\p{Diacritic}/gu,``).toLowerCase().trim()}function i({value:e,moving:t,to:n,items:r}){let i=new Map(r.map(e=>[e.id,e])),a=t.filter(e=>i.has(e)&&!i.get(e)?.disabled),o=new Set(e);for(let e of a)n===`target`?o.add(e):o.delete(e);return r.filter(e=>o.has(e.id)).map(e=>e.id)}function a(e){return e.filter(e=>!e.disabled).map(e=>e.id)}var o={sourceTitle:`Disponíveis`,targetTitle:`Selecionados`,search:`Buscar`,toTarget:`Mover selecionados para a direita`,toSource:`Mover selecionados para a esquerda`,allToTarget:`Mover todos para a direita`,allToSource:`Mover todos para a esquerda`,empty:`Nada aqui`,noMatches:`Nenhum resultado`,selected:(e,t)=>`${e} de ${t} marcados`,moved:(e,t)=>`${e} ${e===1?`item movido`:`itens movidos`} para ${t}`},s={sourceTitle:`Available`,targetTitle:`Selected`,search:`Search`,toTarget:`Move checked to the right`,toSource:`Move checked to the left`,allToTarget:`Move all to the right`,allToSource:`Move all to the left`,empty:`Nothing here`,noMatches:`No matches`,selected:(e,t)=>`${e} of ${t} checked`,moved:(e,t)=>`${e} ${e===1?`item moved`:`items moved`} to ${t}`};function c(e){return e===`en`?s:o}exports.applyMove=i,exports.filterItems=n,exports.movableIds=a,exports.searchTextOf=t,exports.splitSides=e,exports.transferStrings=c;
|
|
2
|
+
//# sourceMappingURL=transfer-state.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transfer-state.cjs","names":[],"sources":["../../../src/components/Transfer/transfer-state.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\n\n/** One movable entry. */\nexport interface TransferItem {\n /** Stable identity. What `value`/`onChange` carry. */\n id: string;\n /** What the row shows. */\n label: ReactNode;\n /** Text the search box matches against. Falls back to `label` when it is a string. */\n searchText?: string;\n /** Row that cannot move — a mandatory permission, a locked seat. */\n disabled?: boolean;\n /** Anything the app wants back in `renderItem`. */\n data?: Record<string, unknown>;\n}\n\n/** Which pane an operation is about. */\nexport type TransferSide = \"source\" | \"target\";\n\n/**\n * Split the catalogue into the two panes.\n *\n * The selected ids are the single source of truth — the panes are derived, never\n * stored. Two stored lists drift the moment the catalogue changes under them: a\n * permission that is removed upstream lingers in whichever pane held it, and an\n * id that appears in both is a bug nobody can see.\n *\n * Order follows `items`, so both panes read in the catalogue's order rather than\n * in the order somebody happened to click.\n *\n * @param items - The whole catalogue.\n * @param value - Ids currently on the target side.\n */\nexport function splitSides(\n items: readonly TransferItem[],\n value: readonly string[],\n): { source: TransferItem[]; target: TransferItem[] } {\n const selected = new Set(value);\n const source: TransferItem[] = [];\n const target: TransferItem[] = [];\n for (const item of items) {\n (selected.has(item.id) ? target : source).push(item);\n }\n return { source, target };\n}\n\n/** Text a row is searched by. */\nexport function searchTextOf(item: TransferItem): string {\n if (item.searchText !== undefined) return item.searchText;\n return typeof item.label === \"string\" ? item.label : \"\";\n}\n\n/**\n * Filter a pane by a query, case- and accent-insensitively.\n *\n * Accent folding is not optional for a PT-BR audience: typing \"sao\" has to find\n * \"São Paulo\", and a plain `includes` would not.\n */\nexport function filterItems(items: readonly TransferItem[], query: string): TransferItem[] {\n const needle = fold(query);\n if (!needle) return [...items];\n return items.filter((item) => fold(searchTextOf(item)).includes(needle));\n}\n\n/** Lowercase and strip diacritics. */\nfunction fold(text: string): string {\n return text\n .normalize(\"NFD\")\n .replace(/\\p{Diacritic}/gu, \"\")\n .toLowerCase()\n .trim();\n}\n\n/**\n * Apply a move and return the next value.\n *\n * Disabled rows are dropped here rather than at the call site, so no caller can\n * move one by pressing the bulk button — the check has to live where the\n * decision is made, not in each of the four places that trigger one.\n *\n * @param params.value - Current target ids.\n * @param params.moving - Ids being moved.\n * @param params.to - Destination pane.\n * @param params.items - The catalogue, to look disabled rows up.\n * @param params.order - Ids in catalogue order, so the result stays stable.\n * @returns The next value, in catalogue order.\n */\nexport function applyMove({\n value,\n moving,\n to,\n items,\n}: {\n value: readonly string[];\n moving: readonly string[];\n to: TransferSide;\n items: readonly TransferItem[];\n}): string[] {\n const byId = new Map(items.map((item) => [item.id, item]));\n const allowed = moving.filter((id) => byId.has(id) && !byId.get(id)?.disabled);\n const next = new Set(value);\n for (const id of allowed) {\n if (to === \"target\") next.add(id);\n else next.delete(id);\n }\n return items.filter((item) => next.has(item.id)).map((item) => item.id);\n}\n\n/** Ids in `items` that a checkbox set may still act on. */\nexport function movableIds(items: readonly TransferItem[]): string[] {\n return items.filter((item) => !item.disabled).map((item) => item.id);\n}\n\n/** Labels the component needs, per locale. */\ninterface TransferStrings {\n sourceTitle: string;\n targetTitle: string;\n search: string;\n toTarget: string;\n toSource: string;\n allToTarget: string;\n allToSource: string;\n empty: string;\n noMatches: string;\n selected: (n: number, total: number) => string;\n moved: (n: number, side: string) => string;\n}\n\nconst PT_BR: TransferStrings = {\n sourceTitle: \"Disponíveis\",\n targetTitle: \"Selecionados\",\n search: \"Buscar\",\n toTarget: \"Mover selecionados para a direita\",\n toSource: \"Mover selecionados para a esquerda\",\n allToTarget: \"Mover todos para a direita\",\n allToSource: \"Mover todos para a esquerda\",\n empty: \"Nada aqui\",\n noMatches: \"Nenhum resultado\",\n selected: (n, total) => `${n} de ${total} marcados`,\n moved: (n, side) => `${n} ${n === 1 ? \"item movido\" : \"itens movidos\"} para ${side}`,\n};\n\nconst EN: TransferStrings = {\n sourceTitle: \"Available\",\n targetTitle: \"Selected\",\n search: \"Search\",\n toTarget: \"Move checked to the right\",\n toSource: \"Move checked to the left\",\n allToTarget: \"Move all to the right\",\n allToSource: \"Move all to the left\",\n empty: \"Nothing here\",\n noMatches: \"No matches\",\n selected: (n, total) => `${n} of ${total} checked`,\n moved: (n, side) => `${n} ${n === 1 ? \"item moved\" : \"items moved\"} to ${side}`,\n};\n\n/** Locale strings for the dual list. */\nexport function transferStrings(locale: \"pt-BR\" | \"en\"): TransferStrings {\n return locale === \"en\" ? EN : PT_BR;\n}\n"],"mappings":"AAiCA,SAAgB,EACZ,EACA,EACkD,CAClD,IAAM,EAAW,IAAI,IAAI,CAAK,EACxB,EAAyB,CAAC,EAC1B,EAAyB,CAAC,EAChC,IAAK,IAAM,KAAQ,GACd,EAAS,IAAI,EAAK,EAAE,EAAI,EAAS,EAAA,CAAQ,KAAK,CAAI,EAEvD,MAAO,CAAE,SAAQ,QAAO,CAC5B,CAGA,SAAgB,EAAa,EAA4B,CAErD,OADI,EAAK,aAAe,IAAA,GACjB,OAAO,EAAK,OAAU,SAAW,EAAK,MAAQ,GADX,EAAK,UAEnD,CAQA,SAAgB,EAAY,EAAgC,EAA+B,CACvF,IAAM,EAAS,EAAK,CAAK,EAEzB,OADK,EACE,EAAM,OAAQ,GAAS,EAAK,EAAa,CAAI,CAAC,CAAC,CAAC,SAAS,CAAM,CAAC,EADnD,CAAC,GAAG,CAAK,CAEjC,CAGA,SAAS,EAAK,EAAsB,CAChC,OAAO,EACF,UAAU,KAAK,CAAC,CAChB,QAAQ,kBAAmB,EAAE,CAAC,CAC9B,YAAY,CAAC,CACb,KAAK,CACd,CAgBA,SAAgB,EAAU,CACtB,QACA,SACA,KACA,SAMS,CACT,IAAM,EAAO,IAAI,IAAI,EAAM,IAAK,GAAS,CAAC,EAAK,GAAI,CAAI,CAAC,CAAC,EACnD,EAAU,EAAO,OAAQ,GAAO,EAAK,IAAI,CAAE,GAAK,CAAC,EAAK,IAAI,CAAE,CAAC,EAAE,QAAQ,EACvE,EAAO,IAAI,IAAI,CAAK,EAC1B,IAAK,IAAM,KAAM,EACT,IAAO,SAAU,EAAK,IAAI,CAAE,EAC3B,EAAK,OAAO,CAAE,EAEvB,OAAO,EAAM,OAAQ,GAAS,EAAK,IAAI,EAAK,EAAE,CAAC,CAAC,CAAC,IAAK,GAAS,EAAK,EAAE,CAC1E,CAGA,SAAgB,EAAW,EAA0C,CACjE,OAAO,EAAM,OAAQ,GAAS,CAAC,EAAK,QAAQ,CAAC,CAAC,IAAK,GAAS,EAAK,EAAE,CACvE,CAiBA,IAAM,EAAyB,CAC3B,YAAa,cACb,YAAa,eACb,OAAQ,SACR,SAAU,oCACV,SAAU,qCACV,YAAa,6BACb,YAAa,8BACb,MAAO,YACP,UAAW,mBACX,UAAW,EAAG,IAAU,GAAG,EAAE,MAAM,EAAM,WACzC,OAAQ,EAAG,IAAS,GAAG,EAAE,GAAG,IAAM,EAAI,cAAgB,gBAAgB,QAAQ,GAClF,EAEM,EAAsB,CACxB,YAAa,YACb,YAAa,WACb,OAAQ,SACR,SAAU,4BACV,SAAU,2BACV,YAAa,wBACb,YAAa,uBACb,MAAO,eACP,UAAW,aACX,UAAW,EAAG,IAAU,GAAG,EAAE,MAAM,EAAM,UACzC,OAAQ,EAAG,IAAS,GAAG,EAAE,GAAG,IAAM,EAAI,aAAe,cAAc,MAAM,GAC7E,EAGA,SAAgB,EAAgB,EAAyC,CACrE,OAAO,IAAW,KAAO,EAAK,CAClC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
//#region src/components/Transfer/transfer-state.ts
|
|
2
|
+
function e(e, t) {
|
|
3
|
+
let n = new Set(t), r = [], i = [];
|
|
4
|
+
for (let t of e) (n.has(t.id) ? i : r).push(t);
|
|
5
|
+
return {
|
|
6
|
+
source: r,
|
|
7
|
+
target: i
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function t(e) {
|
|
11
|
+
return e.searchText === void 0 ? typeof e.label == "string" ? e.label : "" : e.searchText;
|
|
12
|
+
}
|
|
13
|
+
function n(e, n) {
|
|
14
|
+
let i = r(n);
|
|
15
|
+
return i ? e.filter((e) => r(t(e)).includes(i)) : [...e];
|
|
16
|
+
}
|
|
17
|
+
function r(e) {
|
|
18
|
+
return e.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase().trim();
|
|
19
|
+
}
|
|
20
|
+
function i({ value: e, moving: t, to: n, items: r }) {
|
|
21
|
+
let i = new Map(r.map((e) => [e.id, e])), a = t.filter((e) => i.has(e) && !i.get(e)?.disabled), o = new Set(e);
|
|
22
|
+
for (let e of a) n === "target" ? o.add(e) : o.delete(e);
|
|
23
|
+
return r.filter((e) => o.has(e.id)).map((e) => e.id);
|
|
24
|
+
}
|
|
25
|
+
function a(e) {
|
|
26
|
+
return e.filter((e) => !e.disabled).map((e) => e.id);
|
|
27
|
+
}
|
|
28
|
+
var o = {
|
|
29
|
+
sourceTitle: "Disponíveis",
|
|
30
|
+
targetTitle: "Selecionados",
|
|
31
|
+
search: "Buscar",
|
|
32
|
+
toTarget: "Mover selecionados para a direita",
|
|
33
|
+
toSource: "Mover selecionados para a esquerda",
|
|
34
|
+
allToTarget: "Mover todos para a direita",
|
|
35
|
+
allToSource: "Mover todos para a esquerda",
|
|
36
|
+
empty: "Nada aqui",
|
|
37
|
+
noMatches: "Nenhum resultado",
|
|
38
|
+
selected: (e, t) => `${e} de ${t} marcados`,
|
|
39
|
+
moved: (e, t) => `${e} ${e === 1 ? "item movido" : "itens movidos"} para ${t}`
|
|
40
|
+
}, s = {
|
|
41
|
+
sourceTitle: "Available",
|
|
42
|
+
targetTitle: "Selected",
|
|
43
|
+
search: "Search",
|
|
44
|
+
toTarget: "Move checked to the right",
|
|
45
|
+
toSource: "Move checked to the left",
|
|
46
|
+
allToTarget: "Move all to the right",
|
|
47
|
+
allToSource: "Move all to the left",
|
|
48
|
+
empty: "Nothing here",
|
|
49
|
+
noMatches: "No matches",
|
|
50
|
+
selected: (e, t) => `${e} of ${t} checked`,
|
|
51
|
+
moved: (e, t) => `${e} ${e === 1 ? "item moved" : "items moved"} to ${t}`
|
|
52
|
+
};
|
|
53
|
+
function c(e) {
|
|
54
|
+
return e === "en" ? s : o;
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
export { i as applyMove, n as filterItems, a as movableIds, t as searchTextOf, e as splitSides, c as transferStrings };
|
|
58
|
+
|
|
59
|
+
//# sourceMappingURL=transfer-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transfer-state.js","names":[],"sources":["../../../src/components/Transfer/transfer-state.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\n\n/** One movable entry. */\nexport interface TransferItem {\n /** Stable identity. What `value`/`onChange` carry. */\n id: string;\n /** What the row shows. */\n label: ReactNode;\n /** Text the search box matches against. Falls back to `label` when it is a string. */\n searchText?: string;\n /** Row that cannot move — a mandatory permission, a locked seat. */\n disabled?: boolean;\n /** Anything the app wants back in `renderItem`. */\n data?: Record<string, unknown>;\n}\n\n/** Which pane an operation is about. */\nexport type TransferSide = \"source\" | \"target\";\n\n/**\n * Split the catalogue into the two panes.\n *\n * The selected ids are the single source of truth — the panes are derived, never\n * stored. Two stored lists drift the moment the catalogue changes under them: a\n * permission that is removed upstream lingers in whichever pane held it, and an\n * id that appears in both is a bug nobody can see.\n *\n * Order follows `items`, so both panes read in the catalogue's order rather than\n * in the order somebody happened to click.\n *\n * @param items - The whole catalogue.\n * @param value - Ids currently on the target side.\n */\nexport function splitSides(\n items: readonly TransferItem[],\n value: readonly string[],\n): { source: TransferItem[]; target: TransferItem[] } {\n const selected = new Set(value);\n const source: TransferItem[] = [];\n const target: TransferItem[] = [];\n for (const item of items) {\n (selected.has(item.id) ? target : source).push(item);\n }\n return { source, target };\n}\n\n/** Text a row is searched by. */\nexport function searchTextOf(item: TransferItem): string {\n if (item.searchText !== undefined) return item.searchText;\n return typeof item.label === \"string\" ? item.label : \"\";\n}\n\n/**\n * Filter a pane by a query, case- and accent-insensitively.\n *\n * Accent folding is not optional for a PT-BR audience: typing \"sao\" has to find\n * \"São Paulo\", and a plain `includes` would not.\n */\nexport function filterItems(items: readonly TransferItem[], query: string): TransferItem[] {\n const needle = fold(query);\n if (!needle) return [...items];\n return items.filter((item) => fold(searchTextOf(item)).includes(needle));\n}\n\n/** Lowercase and strip diacritics. */\nfunction fold(text: string): string {\n return text\n .normalize(\"NFD\")\n .replace(/\\p{Diacritic}/gu, \"\")\n .toLowerCase()\n .trim();\n}\n\n/**\n * Apply a move and return the next value.\n *\n * Disabled rows are dropped here rather than at the call site, so no caller can\n * move one by pressing the bulk button — the check has to live where the\n * decision is made, not in each of the four places that trigger one.\n *\n * @param params.value - Current target ids.\n * @param params.moving - Ids being moved.\n * @param params.to - Destination pane.\n * @param params.items - The catalogue, to look disabled rows up.\n * @param params.order - Ids in catalogue order, so the result stays stable.\n * @returns The next value, in catalogue order.\n */\nexport function applyMove({\n value,\n moving,\n to,\n items,\n}: {\n value: readonly string[];\n moving: readonly string[];\n to: TransferSide;\n items: readonly TransferItem[];\n}): string[] {\n const byId = new Map(items.map((item) => [item.id, item]));\n const allowed = moving.filter((id) => byId.has(id) && !byId.get(id)?.disabled);\n const next = new Set(value);\n for (const id of allowed) {\n if (to === \"target\") next.add(id);\n else next.delete(id);\n }\n return items.filter((item) => next.has(item.id)).map((item) => item.id);\n}\n\n/** Ids in `items` that a checkbox set may still act on. */\nexport function movableIds(items: readonly TransferItem[]): string[] {\n return items.filter((item) => !item.disabled).map((item) => item.id);\n}\n\n/** Labels the component needs, per locale. */\ninterface TransferStrings {\n sourceTitle: string;\n targetTitle: string;\n search: string;\n toTarget: string;\n toSource: string;\n allToTarget: string;\n allToSource: string;\n empty: string;\n noMatches: string;\n selected: (n: number, total: number) => string;\n moved: (n: number, side: string) => string;\n}\n\nconst PT_BR: TransferStrings = {\n sourceTitle: \"Disponíveis\",\n targetTitle: \"Selecionados\",\n search: \"Buscar\",\n toTarget: \"Mover selecionados para a direita\",\n toSource: \"Mover selecionados para a esquerda\",\n allToTarget: \"Mover todos para a direita\",\n allToSource: \"Mover todos para a esquerda\",\n empty: \"Nada aqui\",\n noMatches: \"Nenhum resultado\",\n selected: (n, total) => `${n} de ${total} marcados`,\n moved: (n, side) => `${n} ${n === 1 ? \"item movido\" : \"itens movidos\"} para ${side}`,\n};\n\nconst EN: TransferStrings = {\n sourceTitle: \"Available\",\n targetTitle: \"Selected\",\n search: \"Search\",\n toTarget: \"Move checked to the right\",\n toSource: \"Move checked to the left\",\n allToTarget: \"Move all to the right\",\n allToSource: \"Move all to the left\",\n empty: \"Nothing here\",\n noMatches: \"No matches\",\n selected: (n, total) => `${n} of ${total} checked`,\n moved: (n, side) => `${n} ${n === 1 ? \"item moved\" : \"items moved\"} to ${side}`,\n};\n\n/** Locale strings for the dual list. */\nexport function transferStrings(locale: \"pt-BR\" | \"en\"): TransferStrings {\n return locale === \"en\" ? EN : PT_BR;\n}\n"],"mappings":";AAiCA,SAAgB,EACZ,GACA,GACkD;CAClD,IAAM,IAAW,IAAI,IAAI,CAAK,GACxB,IAAyB,CAAC,GAC1B,IAAyB,CAAC;CAChC,KAAK,IAAM,KAAQ,GACf,CAAC,EAAS,IAAI,EAAK,EAAE,IAAI,IAAS,EAAA,CAAQ,KAAK,CAAI;CAEvD,OAAO;EAAE;EAAQ;CAAO;AAC5B;AAGA,SAAgB,EAAa,GAA4B;CAErD,OADI,EAAK,eAAe,KAAA,IACjB,OAAO,EAAK,SAAU,WAAW,EAAK,QAAQ,KADX,EAAK;AAEnD;AAQA,SAAgB,EAAY,GAAgC,GAA+B;CACvF,IAAM,IAAS,EAAK,CAAK;CAEzB,OADK,IACE,EAAM,QAAQ,MAAS,EAAK,EAAa,CAAI,CAAC,CAAC,CAAC,SAAS,CAAM,CAAC,IADnD,CAAC,GAAG,CAAK;AAEjC;AAGA,SAAS,EAAK,GAAsB;CAChC,OAAO,EACF,UAAU,KAAK,CAAC,CAChB,QAAQ,mBAAmB,EAAE,CAAC,CAC9B,YAAY,CAAC,CACb,KAAK;AACd;AAgBA,SAAgB,EAAU,EACtB,UACA,WACA,OACA,YAMS;CACT,IAAM,IAAO,IAAI,IAAI,EAAM,KAAK,MAAS,CAAC,EAAK,IAAI,CAAI,CAAC,CAAC,GACnD,IAAU,EAAO,QAAQ,MAAO,EAAK,IAAI,CAAE,KAAK,CAAC,EAAK,IAAI,CAAE,CAAC,EAAE,QAAQ,GACvE,IAAO,IAAI,IAAI,CAAK;CAC1B,KAAK,IAAM,KAAM,GACb,AAAI,MAAO,WAAU,EAAK,IAAI,CAAE,IAC3B,EAAK,OAAO,CAAE;CAEvB,OAAO,EAAM,QAAQ,MAAS,EAAK,IAAI,EAAK,EAAE,CAAC,CAAC,CAAC,KAAK,MAAS,EAAK,EAAE;AAC1E;AAGA,SAAgB,EAAW,GAA0C;CACjE,OAAO,EAAM,QAAQ,MAAS,CAAC,EAAK,QAAQ,CAAC,CAAC,KAAK,MAAS,EAAK,EAAE;AACvE;AAiBA,IAAM,IAAyB;CAC3B,aAAa;CACb,aAAa;CACb,QAAQ;CACR,UAAU;CACV,UAAU;CACV,aAAa;CACb,aAAa;CACb,OAAO;CACP,WAAW;CACX,WAAW,GAAG,MAAU,GAAG,EAAE,MAAM,EAAM;CACzC,QAAQ,GAAG,MAAS,GAAG,EAAE,GAAG,MAAM,IAAI,gBAAgB,gBAAgB,QAAQ;AAClF,GAEM,IAAsB;CACxB,aAAa;CACb,aAAa;CACb,QAAQ;CACR,UAAU;CACV,UAAU;CACV,aAAa;CACb,aAAa;CACb,OAAO;CACP,WAAW;CACX,WAAW,GAAG,MAAU,GAAG,EAAE,MAAM,EAAM;CACzC,QAAQ,GAAG,MAAS,GAAG,EAAE,GAAG,MAAM,IAAI,eAAe,cAAc,MAAM;AAC7E;AAGA,SAAgB,EAAgB,GAAyC;CACrE,OAAO,MAAW,OAAO,IAAK;AAClC"}
|