@bluemarble/bi 0.3.1 → 0.4.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.
@@ -1,6 +1,6 @@
1
- import { A as modelEdges, C as VALUE_FORMATS, D as validateModel, N as referencedTables, S as AGGREGATION_LABEL, T as isAggregate, _ as canonicalJson, a as FILTER_FIELDS, b as toModelCode, c as chartOfType, d as filterReferences, g as validateLayout, i as CARD_FIELDS, j as parseColumnRef, k as measureHome, l as chartReferences, m as layoutModelWarnings, n as CANVAS_PRESETS, p as layoutFileContent, r as CARDS_FIELDS, s as chartColumns, t as BUILTIN_CHART_TYPES, x as AGGREGATIONS } from "./layout-DTR4vI-I.js";
1
+ import { A as modelEdges, C as VALUE_FORMATS, D as validateModel, N as referencedTables, S as AGGREGATION_LABEL, T as isAggregate, _ as canonicalJson, a as FILTER_FIELDS, b as toModelCode, c as chartOfType, d as filterReferences, g as validateLayout, i as CARD_FIELDS, j as parseColumnRef, k as measureHome, l as chartReferences, m as layoutModelWarnings, n as CANVAS_PRESETS, p as layoutFileContent, r as CARDS_FIELDS, s as chartColumns, t as BUILTIN_CHART_TYPES, x as AGGREGATIONS } from "./layout-676sEvvb.js";
2
2
  import { n as diffRows } from "./compare-DloZhg1T.js";
3
- import { a as useDashboard, c as layoutItemKey, l as parseLayoutItemKey, n as INSPECT_ATTRIBUTE, o as errorMessage, s as LAYOUT_ITEM_ATTRIBUTE, t as ModelDictionary } from "./ModelDictionary-kUKcdbc8.js";
3
+ import { a as useDashboard, c as layoutItemKey, l as parseLayoutItemKey, n as INSPECT_ATTRIBUTE, o as errorMessage, s as LAYOUT_ITEM_ATTRIBUTE, t as ModelDictionary } from "./ModelDictionary-Y7W9oK1J.js";
4
4
  import { Alert, Autocomplete, Box, Button, Checkbox, Chip, CircularProgress, Collapse, Dialog, DialogActions, DialogContent, DialogTitle, Divider, FormControlLabel, IconButton, List, ListItemButton, Menu, MenuItem, MenuList, Paper, Popper, Portal, Tab, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TableSortLabel, Tabs, TextField, ThemeProvider, ToggleButton, ToggleButtonGroup, Tooltip, Typography, createTheme } from "@mui/material";
5
5
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
6
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -1656,6 +1656,7 @@ function ChartForm({ chart, cards, types, model, formatters, columns, onChange }
1656
1656
  columns: chartColumns(chart, types),
1657
1657
  formColumns: columns,
1658
1658
  chosenDimensions: chartReferences(chart, types).dimensions,
1659
+ chosenMeasures: chartReferences(chart, types).measures,
1659
1660
  header: /* @__PURE__ */ jsxs(Fragment, { children: [field("Tipo", /* @__PURE__ */ jsxs(TextField, {
1660
1661
  select: true,
1661
1662
  size: "small",
@@ -1703,7 +1704,7 @@ function FilterForm({ filter, model, formatters, columns, onChange }) {
1703
1704
  });
1704
1705
  }
1705
1706
  /** Formulário gerado dos `FieldSpec` (campos do tipo de gráfico ou do filtro). */
1706
- function FieldsForm({ fields, values, onSet: set, model, formatters, columns, formColumns, chosenDimensions, header }) {
1707
+ function FieldsForm({ fields, values, onSet: set, model, formatters, columns, formColumns, chosenDimensions, chosenMeasures = [], header }) {
1707
1708
  const dimensions = Object.entries(model.dimensions).map(([name, dim]) => ({
1708
1709
  name,
1709
1710
  label: dim.label
@@ -1742,37 +1743,11 @@ function FieldsForm({ fields, values, onSet: set, model, formatters, columns, fo
1742
1743
  onChange: (e) => set(key, e.target.value),
1743
1744
  sx: inputSx$1
1744
1745
  });
1745
- case "color": {
1746
- const fallback = spec.default;
1747
- return /* @__PURE__ */ jsxs(Box, {
1748
- sx: {
1749
- display: "flex",
1750
- gap: .5,
1751
- alignItems: "center"
1752
- },
1753
- children: [/* @__PURE__ */ jsx(Box, {
1754
- component: "input",
1755
- type: "color",
1756
- value: value ?? fallback,
1757
- onChange: (e) => set(key, e.target.value),
1758
- sx: {
1759
- width: 32,
1760
- height: 30,
1761
- p: 0,
1762
- border: 0,
1763
- bgcolor: "transparent",
1764
- cursor: "pointer"
1765
- }
1766
- }), /* @__PURE__ */ jsx(TextField, {
1767
- size: "small",
1768
- fullWidth: true,
1769
- value: value ?? "",
1770
- placeholder: fallback,
1771
- onChange: (e) => set(key, e.target.value),
1772
- sx: inputSx$1
1773
- })]
1774
- });
1775
- }
1746
+ case "color": return /* @__PURE__ */ jsx(ColorInput, {
1747
+ value,
1748
+ fallback: spec.default,
1749
+ onCommit: (color) => set(key, color)
1750
+ });
1776
1751
  case "boolean": {
1777
1752
  const fallback = spec.default;
1778
1753
  return /* @__PURE__ */ jsx(FormControlLabel, {
@@ -1863,6 +1838,34 @@ function FieldsForm({ fields, values, onSet: set, model, formatters, columns, fo
1863
1838
  }, dimension))
1864
1839
  });
1865
1840
  }
1841
+ case "labels":
1842
+ if (!columns.length) return /* @__PURE__ */ jsx(Typography, {
1843
+ sx: {
1844
+ fontSize: 12,
1845
+ color: "text.disabled"
1846
+ },
1847
+ children: "sem colunas"
1848
+ });
1849
+ return /* @__PURE__ */ jsx(LabelsEditor, {
1850
+ value: value ?? {},
1851
+ columns,
1852
+ model,
1853
+ onChange: (next) => set(key, next)
1854
+ });
1855
+ case "formats":
1856
+ if (!chosenMeasures.length) return /* @__PURE__ */ jsx(Typography, {
1857
+ sx: {
1858
+ fontSize: 12,
1859
+ color: "text.disabled"
1860
+ },
1861
+ children: "sem medidas"
1862
+ });
1863
+ return /* @__PURE__ */ jsx(FormatsEditor, {
1864
+ value: value ?? {},
1865
+ measures: chosenMeasures,
1866
+ model,
1867
+ onChange: (next) => set(key, next)
1868
+ });
1866
1869
  default: return /* @__PURE__ */ jsx(NameSelect, {
1867
1870
  value: value ?? "",
1868
1871
  options: options(spec),
@@ -1873,7 +1876,7 @@ function FieldsForm({ fields, values, onSet: set, model, formatters, columns, fo
1873
1876
  }
1874
1877
  };
1875
1878
  const sections = [...new Set(entries.map(([, spec]) => spec.section))];
1876
- const wide = (spec, key) => key === "title" || spec.kind === "dimensions" || spec.kind === "measures" || spec.kind === "formatters";
1879
+ const wide = (spec, key) => key === "title" || spec.kind === "dimensions" || spec.kind === "measures" || spec.kind === "formatters" || spec.kind === "labels" || spec.kind === "formats";
1877
1880
  return /* @__PURE__ */ jsx(Fragment, { children: sections.map((section) => {
1878
1881
  const inSection = entries.filter(([, spec]) => spec.section === section);
1879
1882
  return /* @__PURE__ */ jsx(Section, {
@@ -1892,17 +1895,246 @@ function FieldsForm({ fields, values, onSet: set, model, formatters, columns, fo
1892
1895
  }, section);
1893
1896
  }) });
1894
1897
  }
1898
+ /**
1899
+ * Cor com o seletor nativo. Mexer no seletor só muda a amostra: o rascunho (e a página) muda quando
1900
+ * ele fecha — o `change` nativo; o `onChange` do React dispara a cada movimento. O texto aplica no
1901
+ * Enter ou ao sair do campo.
1902
+ */
1903
+ function ColorInput({ value, fallback, onCommit }) {
1904
+ const [picking, setPicking] = useState(null);
1905
+ const [typing, setTyping] = useState(null);
1906
+ const picker = useRef(null);
1907
+ const commit = useRef(onCommit);
1908
+ useEffect(() => {
1909
+ commit.current = onCommit;
1910
+ });
1911
+ useEffect(() => {
1912
+ const input = picker.current;
1913
+ if (!input) return;
1914
+ const listener = () => {
1915
+ commit.current(input.value);
1916
+ setPicking(null);
1917
+ };
1918
+ input.addEventListener("change", listener);
1919
+ return () => input.removeEventListener("change", listener);
1920
+ }, []);
1921
+ const commitText = () => {
1922
+ if (typing === null) return;
1923
+ const text = typing.trim();
1924
+ setTyping(null);
1925
+ if (text !== (value ?? "")) onCommit(text || void 0);
1926
+ };
1927
+ return /* @__PURE__ */ jsxs(Box, {
1928
+ sx: {
1929
+ display: "flex",
1930
+ gap: .5,
1931
+ alignItems: "center"
1932
+ },
1933
+ children: [/* @__PURE__ */ jsx(Box, {
1934
+ component: "input",
1935
+ type: "color",
1936
+ ref: picker,
1937
+ value: picking ?? value ?? fallback,
1938
+ onChange: (e) => setPicking(e.target.value),
1939
+ sx: {
1940
+ width: 32,
1941
+ height: 30,
1942
+ p: 0,
1943
+ border: 0,
1944
+ bgcolor: "transparent",
1945
+ cursor: "pointer"
1946
+ }
1947
+ }), /* @__PURE__ */ jsx(TextField, {
1948
+ size: "small",
1949
+ fullWidth: true,
1950
+ value: typing ?? picking ?? value ?? "",
1951
+ placeholder: fallback,
1952
+ onChange: (e) => setTyping(e.target.value),
1953
+ onBlur: commitText,
1954
+ onKeyDown: (e) => {
1955
+ if (e.key === "Enter") commitText();
1956
+ if (e.key === "Escape") setTyping(null);
1957
+ },
1958
+ sx: inputSx$1
1959
+ })]
1960
+ });
1961
+ }
1962
+ /** Sem as chaves de colunas que saíram do gráfico; vazio = `undefined` (sai do JSON). */
1963
+ function pruned(map, keep) {
1964
+ const next = Object.fromEntries(Object.entries(map).filter(([key]) => keep.includes(key)));
1965
+ return Object.keys(next).length ? next : void 0;
1966
+ }
1967
+ const columnNameSx = {
1968
+ fontFamily: MONO,
1969
+ fontSize: 12,
1970
+ minWidth: 90,
1971
+ maxWidth: 140,
1972
+ overflow: "hidden",
1973
+ textOverflow: "ellipsis",
1974
+ whiteSpace: "nowrap"
1975
+ };
1976
+ /** Rótulo de cada coluna do gráfico; o placeholder é o rótulo do dicionário. */
1977
+ function LabelsEditor({ value, columns, model, onChange }) {
1978
+ return /* @__PURE__ */ jsx(Box, {
1979
+ sx: {
1980
+ display: "flex",
1981
+ flexDirection: "column",
1982
+ gap: .5
1983
+ },
1984
+ children: columns.map((column) => /* @__PURE__ */ jsxs(Box, {
1985
+ sx: {
1986
+ display: "flex",
1987
+ gap: .5,
1988
+ alignItems: "center"
1989
+ },
1990
+ children: [/* @__PURE__ */ jsx(Tooltip, {
1991
+ title: model.dimensions[column] ? "dimensão" : "medida",
1992
+ placement: "left",
1993
+ children: /* @__PURE__ */ jsx(Typography, {
1994
+ sx: columnNameSx,
1995
+ children: column
1996
+ })
1997
+ }), /* @__PURE__ */ jsx(TextField, {
1998
+ size: "small",
1999
+ fullWidth: true,
2000
+ value: value[column] ?? "",
2001
+ placeholder: model.dimensions[column]?.label ?? model.measures[column]?.label ?? column,
2002
+ onChange: (e) => onChange(pruned(withField(value, column, e.target.value), columns)),
2003
+ sx: inputSx$1
2004
+ })]
2005
+ }, column))
2006
+ });
2007
+ }
2008
+ const FORMAT_LABEL = {
2009
+ number: "número",
2010
+ integer: "inteiro",
2011
+ currency: "R$",
2012
+ percent: "%"
2013
+ };
2014
+ /** Formato de cada medida do gráfico; vazio = o do dicionário (que aparece como placeholder). */
2015
+ function FormatsEditor({ value, measures, model, onChange }) {
2016
+ const setFormat = (measure, key, next) => {
2017
+ const format = withField(value[measure] ?? {}, key, next);
2018
+ const map = { ...value };
2019
+ if (Object.keys(format).length) map[measure] = format;
2020
+ else delete map[measure];
2021
+ onChange(pruned(map, measures));
2022
+ };
2023
+ const small = {
2024
+ ...inputSx$1,
2025
+ "& .MuiInputBase-input": {
2026
+ fontFamily: MONO,
2027
+ fontSize: 12,
2028
+ px: 1
2029
+ }
2030
+ };
2031
+ return /* @__PURE__ */ jsx(Box, {
2032
+ sx: {
2033
+ display: "flex",
2034
+ flexDirection: "column",
2035
+ gap: .75
2036
+ },
2037
+ children: measures.map((measure) => {
2038
+ const format = value[measure] ?? {};
2039
+ const fromModel = model.measures[measure];
2040
+ const decimals = format.decimals;
2041
+ return /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsxs(Typography, {
2042
+ sx: {
2043
+ ...columnNameSx,
2044
+ maxWidth: "none",
2045
+ mb: .25
2046
+ },
2047
+ children: [measure, fromModel?.label && /* @__PURE__ */ jsx(Box, {
2048
+ component: "span",
2049
+ sx: {
2050
+ color: "text.secondary",
2051
+ ml: 1,
2052
+ fontFamily: "inherit"
2053
+ },
2054
+ children: fromModel.label
2055
+ })]
2056
+ }), /* @__PURE__ */ jsxs(Box, {
2057
+ sx: {
2058
+ display: "grid",
2059
+ gap: .5,
2060
+ alignItems: "center",
2061
+ gridTemplateColumns: "minmax(88px, 1.4fr) minmax(52px, .8fr) 1fr 1fr auto"
2062
+ },
2063
+ children: [
2064
+ /* @__PURE__ */ jsxs(TextField, {
2065
+ select: true,
2066
+ size: "small",
2067
+ value: format.format ?? "",
2068
+ onChange: (e) => setFormat(measure, "format", e.target.value || void 0),
2069
+ slotProps: { select: { displayEmpty: true } },
2070
+ sx: small,
2071
+ children: [/* @__PURE__ */ jsx(MenuItem, {
2072
+ value: "",
2073
+ children: /* @__PURE__ */ jsxs("em", { children: ["padrão", fromModel?.format ? ` (${FORMAT_LABEL[fromModel.format]})` : ""] })
2074
+ }), VALUE_FORMATS.map((name) => /* @__PURE__ */ jsx(MenuItem, {
2075
+ value: name,
2076
+ children: FORMAT_LABEL[name]
2077
+ }, name))]
2078
+ }),
2079
+ /* @__PURE__ */ jsx(Tooltip, {
2080
+ title: "Casas decimais (0 a 10)",
2081
+ children: /* @__PURE__ */ jsx(TextField, {
2082
+ size: "small",
2083
+ type: "number",
2084
+ value: decimals ?? "",
2085
+ placeholder: fromModel?.decimals === void 0 ? ",00" : String(fromModel.decimals),
2086
+ error: decimals !== void 0 && (!Number.isInteger(decimals) || decimals < 0 || decimals > 10),
2087
+ onChange: (e) => setFormat(measure, "decimals", e.target.value.trim() === "" ? void 0 : Number(e.target.value)),
2088
+ slotProps: { htmlInput: {
2089
+ min: 0,
2090
+ max: 10
2091
+ } },
2092
+ sx: small
2093
+ })
2094
+ }),
2095
+ /* @__PURE__ */ jsx(TextField, {
2096
+ size: "small",
2097
+ value: format.prefix ?? "",
2098
+ placeholder: "prefixo",
2099
+ onChange: (e) => setFormat(measure, "prefix", e.target.value),
2100
+ sx: small
2101
+ }),
2102
+ /* @__PURE__ */ jsx(TextField, {
2103
+ size: "small",
2104
+ value: format.suffix ?? "",
2105
+ placeholder: "sufixo",
2106
+ onChange: (e) => setFormat(measure, "suffix", e.target.value),
2107
+ sx: small
2108
+ }),
2109
+ /* @__PURE__ */ jsx(Tooltip, {
2110
+ title: "Notação curta: 1,2 mil / 3,4 mi",
2111
+ children: /* @__PURE__ */ jsx(FormControlLabel, {
2112
+ sx: { m: 0 },
2113
+ control: /* @__PURE__ */ jsx(Checkbox, {
2114
+ size: "small",
2115
+ sx: { p: .25 },
2116
+ checked: format.compact ?? false,
2117
+ onChange: (e) => setFormat(measure, "compact", e.target.checked || void 0)
2118
+ }),
2119
+ label: /* @__PURE__ */ jsx(Typography, {
2120
+ sx: { fontSize: 11 },
2121
+ children: "curto"
2122
+ })
2123
+ })
2124
+ })
2125
+ ]
2126
+ })] }, measure);
2127
+ })
2128
+ });
2129
+ }
1895
2130
  const formGrid = (columns) => ({
1896
2131
  display: "grid",
1897
2132
  gap: 1.5,
1898
2133
  gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`
1899
2134
  });
1900
- /** Rótulo padrão (do dicionário) como placeholder dos campos `*Label` do combo. */
2135
+ /** Rótulo padrão (do dicionário) como placeholder do `label` do filtro. */
1901
2136
  function labelPlaceholder(key, values, model) {
1902
2137
  if (key === "label" && typeof values.dimension === "string") return model.dimensions[values.dimension]?.label ?? values.dimension;
1903
- const measureKey = key.endsWith("Label") ? key.slice(0, -5) : null;
1904
- const measure = measureKey && values[measureKey];
1905
- return typeof measure === "string" ? model.measures[measure]?.label ?? measure : void 0;
1906
2138
  }
1907
2139
  function field(label, input, hint) {
1908
2140
  return /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsxs(Typography, {
@@ -2034,10 +2266,7 @@ function toEditor(model) {
2034
2266
  const uidOf = (name) => tables.find((table) => table.name === name)?.uid ?? "";
2035
2267
  const positions = Object.fromEntries(Object.entries(model.diagram ?? {}).flatMap(([name, point]) => {
2036
2268
  const uid = uidOf(name);
2037
- return uid ? [[uid, {
2038
- x: point.x,
2039
- y: point.y
2040
- }]] : [];
2269
+ return uid ? [[uid, { ...point }]] : [];
2041
2270
  }));
2042
2271
  return {
2043
2272
  name: model.name,
@@ -2132,14 +2361,18 @@ function fromEditor(editor) {
2132
2361
  ...Object.keys(diagram).length && { diagram }
2133
2362
  };
2134
2363
  }
2135
- /** Posições pelo nome, na ordem dos cards; arredonda (o arquivo não precisa de frações). */
2364
+ /** Posições (e tamanhos) pelo nome, na ordem dos cards; arredonda (o arquivo não precisa de frações). */
2136
2365
  function diagramOf(editor) {
2137
2366
  return Object.fromEntries(editor.tables.flatMap(({ uid, name }) => {
2138
2367
  const point = editor.positions[uid];
2139
- return point && name ? [[name, {
2140
- x: Math.round(point.x),
2141
- y: Math.round(point.y)
2142
- }]] : [];
2368
+ if (!point || !name) return [];
2369
+ const { x, y, width, height } = point;
2370
+ return [[name, {
2371
+ x: Math.round(x),
2372
+ y: Math.round(y),
2373
+ ...width !== void 0 && { width: Math.round(width) },
2374
+ ...height !== void 0 && { height: Math.round(height) }
2375
+ }]];
2143
2376
  }));
2144
2377
  }
2145
2378
  /** O modelo sem as posições do diagrama: o que muda as consultas. */
@@ -2918,6 +3151,11 @@ const HEADER = 34;
2918
3151
  const ROW = 22;
2919
3152
  const MAX_BODY = 264;
2920
3153
  const PADDING = 4;
3154
+ /** Limites do redimensionamento do card. */
3155
+ const MIN_WIDTH = 160;
3156
+ const MAX_WIDTH = 800;
3157
+ const MIN_HEIGHT$1 = 64;
3158
+ const MAX_HEIGHT = 1600;
2921
3159
  const ROLE_STYLE = {
2922
3160
  one: {
2923
3161
  color: "#4dabf7",
@@ -3034,25 +3272,27 @@ function buildNodes(editor, columns, errors) {
3034
3272
  };
3035
3273
  });
3036
3274
  }
3037
- const cardHeight = (node, collapsed) => collapsed ? HEADER : HEADER + Math.min(node.items.length * ROW, MAX_BODY) + 8;
3275
+ /** Altura do conteúdo (sem tamanho salvo): até `MAX_BODY` de linhas, o resto rola. */
3276
+ const contentHeight = (node) => HEADER + Math.min(node.items.length * ROW, MAX_BODY) + 8;
3277
+ const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
3038
3278
  /** Quem só filtra (lado 1) em cima, o resto embaixo (como costuma ficar no Power BI). */
3039
- function autoLayout(nodes, heights) {
3279
+ function autoLayout(nodes, sizes) {
3040
3280
  const top = nodes.filter((node) => node.role === "one");
3041
3281
  const bottom = nodes.filter((node) => node.role !== "one");
3042
3282
  const positions = {};
3043
- top.forEach((node, index) => {
3044
- positions[node.id] = {
3045
- x: 40 + index * 290,
3046
- y: 40
3047
- };
3048
- });
3049
- const topHeight = Math.max(0, ...top.map((node) => heights.get(node.id) ?? HEADER));
3050
- bottom.forEach((node, index) => {
3051
- positions[node.id] = {
3052
- x: 40 + index * 290,
3053
- y: 40 + topHeight + 140
3054
- };
3055
- });
3283
+ const gap = 60;
3284
+ const row = (list, y) => {
3285
+ let x = 40;
3286
+ for (const node of list) {
3287
+ positions[node.id] = {
3288
+ x,
3289
+ y
3290
+ };
3291
+ x += (sizes.get(node.id)?.width ?? CARD_WIDTH) + gap;
3292
+ }
3293
+ };
3294
+ row(top, 40);
3295
+ row(bottom, 40 + Math.max(0, ...top.map((node) => sizes.get(node.id)?.height ?? HEADER)) + 140);
3056
3296
  return positions;
3057
3297
  }
3058
3298
  function ModelDiagram({ editor, setEditor, columns, errors, warnings, loadingColumns, onLoadColumns, onTry, usage }) {
@@ -3062,6 +3302,7 @@ function ModelDiagram({ editor, setEditor, columns, errors, warnings, loadingCol
3062
3302
  errors
3063
3303
  ]);
3064
3304
  const [moving, setMoving] = useState(null);
3305
+ const [resizing, setResizing] = useState(null);
3065
3306
  const [collapsed, setCollapsed] = useState({});
3066
3307
  const [scrolls, setScrolls] = useState({});
3067
3308
  const [view, setView] = useState({
@@ -3099,9 +3340,39 @@ function ModelDiagram({ editor, setEditor, columns, errors, warnings, loadingCol
3099
3340
  requested.current = true;
3100
3341
  onLoadColumns();
3101
3342
  }, [columns, onLoadColumns]);
3102
- const heights = useMemo(() => new Map(nodes.map((node) => [node.id, cardHeight(node, !!collapsed[node.id])])), [nodes, collapsed]);
3103
- const fallback = useMemo(() => autoLayout(nodes, heights), [nodes, heights]);
3104
- const placed = (node) => (moving?.id === node.id ? moving.point : editor.positions[node.id]) ?? fallback[node.id];
3343
+ const sizes = useMemo(() => new Map(nodes.map((node) => {
3344
+ const saved = resizing?.id === node.id ? resizing : editor.positions[node.id];
3345
+ const width = saved?.width ?? CARD_WIDTH;
3346
+ if (collapsed[node.id]) return [node.id, {
3347
+ width,
3348
+ height: HEADER
3349
+ }];
3350
+ return [node.id, {
3351
+ width,
3352
+ height: saved?.height ?? contentHeight(node)
3353
+ }];
3354
+ })), [
3355
+ nodes,
3356
+ collapsed,
3357
+ editor.positions,
3358
+ resizing
3359
+ ]);
3360
+ const sizeOf = (node) => sizes.get(node.id) ?? {
3361
+ width: CARD_WIDTH,
3362
+ height: HEADER
3363
+ };
3364
+ const fallback = useMemo(() => autoLayout(nodes, sizes), [nodes, sizes]);
3365
+ const placed = (node) => {
3366
+ if (moving?.id === node.id) return moving.point;
3367
+ const saved = editor.positions[node.id];
3368
+ return saved ? {
3369
+ x: saved.x,
3370
+ y: saved.y
3371
+ } : fallback[node.id] ?? {
3372
+ x: 0,
3373
+ y: 0
3374
+ };
3375
+ };
3105
3376
  const setPositions = (update) => setEditor((current) => ({
3106
3377
  ...current,
3107
3378
  positions: update(current.positions)
@@ -3110,7 +3381,7 @@ function ModelDiagram({ editor, setEditor, columns, errors, warnings, loadingCol
3110
3381
  /** Ponto da linha na coluna do card (lado esquerdo ou direito). */
3111
3382
  const anchor = (node, column, side) => {
3112
3383
  const { x, y } = placed(node);
3113
- const height = heights.get(node.id) ?? HEADER;
3384
+ const { width, height } = sizeOf(node);
3114
3385
  const index = node.items.findIndex((item) => item.kind === "column" && item.name === column);
3115
3386
  let rowY = y + HEADER / 2;
3116
3387
  if (!collapsed[node.id] && index >= 0) {
@@ -3118,7 +3389,7 @@ function ModelDiagram({ editor, setEditor, columns, errors, warnings, loadingCol
3118
3389
  rowY = Math.min(Math.max(raw, y + HEADER + 2), y + height - 2);
3119
3390
  }
3120
3391
  return {
3121
- x: side === "left" ? x : x + CARD_WIDTH,
3392
+ x: side === "left" ? x : x + width,
3122
3393
  y: rowY
3123
3394
  };
3124
3395
  };
@@ -3298,11 +3569,56 @@ function ModelDiagram({ editor, setEditor, columns, errors, warnings, loadingCol
3298
3569
  setMoving(null);
3299
3570
  if (moved) setPositions((current) => ({
3300
3571
  ...current,
3301
- [node.id]: point
3572
+ [node.id]: {
3573
+ ...current[node.id],
3574
+ ...point
3575
+ }
3302
3576
  }));
3303
3577
  else selectNode(node);
3304
3578
  });
3305
3579
  };
3580
+ /** Arrasta a borda direita (`width`), a de baixo (`height`) ou o canto (as duas). */
3581
+ const startResize = (event, node, edges) => {
3582
+ if (event.button !== 0) return;
3583
+ const start = sizeOf(node);
3584
+ const { zoom } = view;
3585
+ let size = start;
3586
+ track(event, (dx, dy) => {
3587
+ size = {
3588
+ width: edges.width ? clamp(start.width + dx / zoom, MIN_WIDTH, MAX_WIDTH) : start.width,
3589
+ height: edges.height ? clamp(start.height + dy / zoom, MIN_HEIGHT$1, MAX_HEIGHT) : start.height
3590
+ };
3591
+ setResizing({
3592
+ id: node.id,
3593
+ ...size
3594
+ });
3595
+ }, (_, moved) => {
3596
+ setResizing(null);
3597
+ if (!moved) return;
3598
+ const point = placed(node);
3599
+ setPositions((current) => ({
3600
+ ...current,
3601
+ [node.id]: {
3602
+ ...point,
3603
+ ...current[node.id],
3604
+ ...edges.width && { width: size.width },
3605
+ ...edges.height && { height: size.height }
3606
+ }
3607
+ }));
3608
+ });
3609
+ };
3610
+ /** Duplo clique na alça: volta ao tamanho automático (largura padrão, altura do conteúdo). */
3611
+ const resetSize = (node) => setPositions((current) => {
3612
+ const saved = current[node.id];
3613
+ if (!saved) return current;
3614
+ return {
3615
+ ...current,
3616
+ [node.id]: {
3617
+ x: saved.x,
3618
+ y: saved.y
3619
+ }
3620
+ };
3621
+ });
3306
3622
  const startLink = (event, node, column) => {
3307
3623
  if (event.button !== 0) return;
3308
3624
  track(event, (_, __, moveEvent) => setLinking({
@@ -3355,11 +3671,12 @@ function ModelDiagram({ editor, setEditor, columns, errors, warnings, loadingCol
3355
3671
  if (!rect || !nodes.length) return;
3356
3672
  const boxes = nodes.map((node) => ({
3357
3673
  ...layout?.[node.id] ?? placed(node),
3358
- h: heights.get(node.id) ?? HEADER
3674
+ w: sizeOf(node).width,
3675
+ h: sizeOf(node).height
3359
3676
  }));
3360
3677
  const minX = Math.min(...boxes.map((box) => box.x));
3361
3678
  const minY = Math.min(...boxes.map((box) => box.y));
3362
- const maxX = Math.max(...boxes.map((box) => box.x + CARD_WIDTH));
3679
+ const maxX = Math.max(...boxes.map((box) => box.x + box.w));
3363
3680
  const maxY = Math.max(...boxes.map((box) => box.y + box.h));
3364
3681
  const zoom = Math.min(1.2, Math.max(.3, Math.min((rect.width - 60) / (maxX - minX), (rect.height - 60) / (maxY - minY))));
3365
3682
  setView({
@@ -3375,7 +3692,7 @@ function ModelDiagram({ editor, setEditor, columns, errors, warnings, loadingCol
3375
3692
  const isSelected = (node) => selection?.kind === "table" && selection.uid === node.uid;
3376
3693
  const lines = [];
3377
3694
  const pushLine = (id, many, manyColumn, one, oneColumn, extra) => {
3378
- const rightward = placed(many).x + CARD_WIDTH / 2 <= placed(one).x + CARD_WIDTH / 2;
3695
+ const rightward = placed(many).x + sizeOf(many).width / 2 <= placed(one).x + sizeOf(one).width / 2;
3379
3696
  lines.push({
3380
3697
  id,
3381
3698
  from: anchor(many, manyColumn, rightward ? "right" : "left"),
@@ -3462,7 +3779,7 @@ function ModelDiagram({ editor, setEditor, columns, errors, warnings, loadingCol
3462
3779
  }), nodes.map((node) => /* @__PURE__ */ jsx(TableCard, {
3463
3780
  node,
3464
3781
  position: placed(node),
3465
- height: heights.get(node.id) ?? HEADER,
3782
+ size: sizeOf(node),
3466
3783
  collapsed: !!collapsed[node.id],
3467
3784
  selected: !!isSelected(node),
3468
3785
  linking: !!linking && linking.node.id !== node.id,
@@ -3476,6 +3793,8 @@ function ModelDiagram({ editor, setEditor, columns, errors, warnings, loadingCol
3476
3793
  [node.id]: top
3477
3794
  })),
3478
3795
  onHeaderDown: (event) => startMove(event, node),
3796
+ onResizeDown: (event, edges) => startResize(event, node, edges),
3797
+ onResizeReset: () => resetSize(node),
3479
3798
  onColumnDown: (event, column) => startLink(event, node, column),
3480
3799
  onFieldClick: (item) => setSelection({
3481
3800
  kind: item.kind,
@@ -3532,8 +3851,11 @@ function ModelDiagram({ editor, setEditor, columns, errors, warnings, loadingCol
3532
3851
  /* @__PURE__ */ jsx(Button, {
3533
3852
  size: "small",
3534
3853
  onClick: () => {
3535
- const layout = autoLayout(nodes, heights);
3536
- setPositions(() => layout);
3854
+ const layout = autoLayout(nodes, sizes);
3855
+ setPositions((current) => Object.fromEntries(Object.entries(layout).map(([id, point]) => [id, {
3856
+ ...current[id],
3857
+ ...point
3858
+ }])));
3537
3859
  fitToScreen(layout);
3538
3860
  },
3539
3861
  title: "Dimensões em cima, fatos embaixo (troca as posições salvas)",
@@ -3842,7 +4164,7 @@ function RelationshipLine({ line, hovered, onHover }) {
3842
4164
  ]
3843
4165
  });
3844
4166
  }
3845
- function TableCard({ node, position, height, collapsed, selected, linking, selectedField, onToggle, onScroll, onHeaderDown, onColumnDown, onFieldClick, onAddField }) {
4167
+ function TableCard({ node, position, size, collapsed, selected, linking, selectedField, onToggle, onScroll, onHeaderDown, onResizeDown, onResizeReset, onColumnDown, onFieldClick, onAddField }) {
3846
4168
  const style = ROLE_STYLE[node.role];
3847
4169
  const columnActions = ["dimension", "measure"];
3848
4170
  return /* @__PURE__ */ jsxs(Box, {
@@ -3851,8 +4173,8 @@ function TableCard({ node, position, height, collapsed, selected, linking, selec
3851
4173
  position: "absolute",
3852
4174
  left: position.x,
3853
4175
  top: position.y,
3854
- width: CARD_WIDTH,
3855
- height,
4176
+ width: size.width,
4177
+ height: size.height,
3856
4178
  bgcolor: "#23272e",
3857
4179
  border: "1px solid",
3858
4180
  borderColor: node.error ? "error.main" : selected ? style.color : "#3a3f47",
@@ -3864,187 +4186,252 @@ function TableCard({ node, position, height, collapsed, selected, linking, selec
3864
4186
  userSelect: "none",
3865
4187
  cursor: "default"
3866
4188
  },
3867
- children: [/* @__PURE__ */ jsxs(Box, {
3868
- onMouseDown: onHeaderDown,
3869
- title: node.error,
3870
- sx: {
3871
- height: 31,
3872
- display: "flex",
3873
- alignItems: "center",
3874
- gap: .75,
3875
- px: 1,
3876
- cursor: "move",
3877
- borderBottom: collapsed ? 0 : "1px solid #3a3f47",
3878
- flexShrink: 0
3879
- },
3880
- children: [
3881
- /* @__PURE__ */ jsx(Box, {
3882
- component: "span",
3883
- sx: {
3884
- color: style.color,
3885
- fontSize: 13
3886
- },
3887
- children: "▦"
3888
- }),
3889
- /* @__PURE__ */ jsx(Typography, {
3890
- sx: {
3891
- fontWeight: 700,
3892
- fontSize: 13,
3893
- overflow: "hidden",
3894
- textOverflow: "ellipsis",
3895
- whiteSpace: "nowrap",
3896
- flex: 1
3897
- },
3898
- children: node.name || /* @__PURE__ */ jsx("em", { children: "sem nome" })
3899
- }),
3900
- /* @__PURE__ */ jsx(Box, {
3901
- component: "span",
3902
- title: style.title,
3903
- sx: {
3904
- fontSize: 10,
3905
- color: style.color
3906
- },
3907
- children: style.label
3908
- }),
3909
- node.error && /* @__PURE__ */ jsx(Box, {
3910
- component: "span",
3911
- sx: {
3912
- color: "error.main",
3913
- fontSize: 12
3914
- },
3915
- children: "⚠"
3916
- }),
3917
- /* @__PURE__ */ jsx(Box, {
3918
- component: "button",
3919
- type: "button",
3920
- "aria-label": collapsed ? "Expandir" : "Recolher",
3921
- onMouseDown: (event) => event.stopPropagation(),
3922
- onClick: onToggle,
3923
- sx: {
3924
- border: 0,
3925
- bgcolor: "transparent",
3926
- color: "text.secondary",
3927
- cursor: "pointer",
3928
- p: 0,
3929
- fontSize: 11
3930
- },
3931
- children: collapsed ? "▾" : "▴"
3932
- })
3933
- ]
3934
- }), !collapsed && /* @__PURE__ */ jsxs(Box, {
3935
- onScroll: (event) => onScroll(event.currentTarget.scrollTop),
3936
- sx: {
3937
- overflow: "auto",
3938
- flex: 1,
3939
- py: `${PADDING}px`
3940
- },
3941
- children: [!node.items.length && /* @__PURE__ */ jsx(Typography, {
3942
- sx: {
3943
- fontSize: 11,
3944
- color: "text.disabled",
3945
- px: 1,
3946
- lineHeight: `${ROW}px`
3947
- },
3948
- children: "sem colunas lidas"
3949
- }), node.items.map((item) => item.kind === "column" ? /* @__PURE__ */ jsxs(Box, {
3950
- "data-diagram-node": node.id,
3951
- "data-diagram-column": item.name,
3952
- onMouseDown: (event) => onColumnDown(event, item.name),
3953
- title: item.missing ? "Citada no modelo, mas a tabela não devolve essa coluna" : "Arraste até a coluna de outra tabela para relacionar",
4189
+ children: [
4190
+ /* @__PURE__ */ jsxs(Box, {
4191
+ onMouseDown: onHeaderDown,
4192
+ title: node.error,
3954
4193
  sx: {
3955
- height: ROW,
4194
+ height: 31,
3956
4195
  display: "flex",
3957
4196
  alignItems: "center",
3958
4197
  gap: .75,
3959
4198
  px: 1,
3960
- fontSize: 12,
3961
- cursor: "crosshair",
3962
- "& .column-actions": { display: "none" },
3963
- "&:hover .column-actions": { display: linking ? "none" : "flex" },
3964
- color: item.missing ? "error.main" : item.linked ? "text.primary" : "#adb5bd",
3965
- fontWeight: item.linked ? 700 : 400,
3966
- "&:hover": { bgcolor: linking ? "rgba(245,130,32,.25)" : "rgba(255,255,255,.06)" }
4199
+ cursor: "move",
4200
+ borderBottom: collapsed ? 0 : "1px solid #3a3f47",
4201
+ flexShrink: 0
3967
4202
  },
3968
4203
  children: [
3969
4204
  /* @__PURE__ */ jsx(Box, {
3970
4205
  component: "span",
3971
4206
  sx: {
3972
- width: 14,
3973
- textAlign: "center",
3974
- color: "text.secondary"
4207
+ color: style.color,
4208
+ fontSize: 13
3975
4209
  },
3976
- children: item.isKey ? "🔑" : item.numeric ? "Σ" : ""
4210
+ children: "▦"
3977
4211
  }),
3978
- /* @__PURE__ */ jsx(Box, {
3979
- component: "span",
4212
+ /* @__PURE__ */ jsx(Typography, {
3980
4213
  sx: {
4214
+ fontWeight: 700,
4215
+ fontSize: 13,
3981
4216
  overflow: "hidden",
3982
4217
  textOverflow: "ellipsis",
3983
- whiteSpace: "nowrap"
4218
+ whiteSpace: "nowrap",
4219
+ flex: 1
3984
4220
  },
3985
- children: item.name
4221
+ children: node.name || /* @__PURE__ */ jsx("em", { children: "sem nome" })
3986
4222
  }),
3987
4223
  /* @__PURE__ */ jsx(Box, {
3988
- className: "column-actions",
4224
+ component: "span",
4225
+ title: style.title,
3989
4226
  sx: {
3990
- ml: "auto",
3991
- gap: .25
4227
+ fontSize: 10,
4228
+ color: style.color
3992
4229
  },
3993
- children: columnActions.map((kind) => /* @__PURE__ */ jsx(Box, {
3994
- component: "button",
3995
- type: "button",
3996
- title: kind === "dimension" ? `Nova dimensão com ${node.name}.${item.name}` : `Nova medida: ${item.numeric ? "soma" : "contagem"} de ${node.name}.${item.name}`,
3997
- onMouseDown: (event) => event.stopPropagation(),
3998
- onClick: () => onAddField(kind, item),
3999
- sx: {
4000
- border: "1px solid #3a3f47",
4001
- borderRadius: .5,
4002
- bgcolor: "#1b1e23",
4003
- color: kind === "measure" ? "#ffd8a8" : "#a5d8ff",
4004
- cursor: "pointer",
4005
- fontSize: 10,
4006
- lineHeight: "14px",
4007
- px: .5
4008
- },
4009
- children: kind === "dimension" ? "+◇" : "+Σ"
4010
- }, kind))
4230
+ children: style.label
4231
+ }),
4232
+ node.error && /* @__PURE__ */ jsx(Box, {
4233
+ component: "span",
4234
+ sx: {
4235
+ color: "error.main",
4236
+ fontSize: 12
4237
+ },
4238
+ children: "⚠"
4239
+ }),
4240
+ /* @__PURE__ */ jsx(Box, {
4241
+ component: "button",
4242
+ type: "button",
4243
+ "aria-label": collapsed ? "Expandir" : "Recolher",
4244
+ onMouseDown: (event) => event.stopPropagation(),
4245
+ onClick: onToggle,
4246
+ sx: {
4247
+ border: 0,
4248
+ bgcolor: "transparent",
4249
+ color: "text.secondary",
4250
+ cursor: "pointer",
4251
+ p: 0,
4252
+ fontSize: 11
4253
+ },
4254
+ children: collapsed ? "▾" : "▴"
4011
4255
  })
4012
4256
  ]
4013
- }, `c:${item.name}`) : /* @__PURE__ */ jsxs(Box, {
4014
- onMouseDown: (event) => event.stopPropagation(),
4015
- onClick: () => onFieldClick(item),
4257
+ }),
4258
+ !collapsed && /* @__PURE__ */ jsxs(Box, {
4259
+ onScroll: (event) => onScroll(event.currentTarget.scrollTop),
4016
4260
  sx: {
4017
- height: ROW,
4018
- display: "flex",
4019
- alignItems: "center",
4020
- gap: .75,
4021
- px: 1,
4022
- fontSize: 12,
4023
- cursor: "pointer",
4024
- color: item.kind === "measure" ? "#ffd8a8" : "#a5d8ff",
4025
- bgcolor: selectedField === item.uid ? "rgba(245,130,32,.2)" : void 0,
4026
- "&:hover": { bgcolor: "rgba(255,255,255,.06)" }
4261
+ overflow: "auto",
4262
+ flex: 1,
4263
+ py: `${PADDING}px`
4027
4264
  },
4028
- children: [/* @__PURE__ */ jsx(Box, {
4029
- component: "span",
4265
+ children: [!node.items.length && /* @__PURE__ */ jsx(Typography, {
4030
4266
  sx: {
4031
- width: 14,
4032
- textAlign: "center"
4267
+ fontSize: 11,
4268
+ color: "text.disabled",
4269
+ px: 1,
4270
+ lineHeight: `${ROW}px`
4033
4271
  },
4034
- children: item.icon
4035
- }), /* @__PURE__ */ jsx(Box, {
4036
- component: "span",
4272
+ children: "sem colunas lidas"
4273
+ }), node.items.map((item) => item.kind === "column" ? /* @__PURE__ */ jsxs(Box, {
4274
+ "data-diagram-node": node.id,
4275
+ "data-diagram-column": item.name,
4276
+ onMouseDown: (event) => onColumnDown(event, item.name),
4277
+ title: item.missing ? "Citada no modelo, mas a tabela não devolve essa coluna" : "Arraste até a coluna de outra tabela para relacionar",
4037
4278
  sx: {
4038
- overflow: "hidden",
4039
- textOverflow: "ellipsis",
4040
- whiteSpace: "nowrap"
4279
+ height: ROW,
4280
+ display: "flex",
4281
+ alignItems: "center",
4282
+ gap: .75,
4283
+ px: 1,
4284
+ fontSize: 12,
4285
+ cursor: "crosshair",
4286
+ "& .column-actions": { display: "none" },
4287
+ "&:hover .column-actions": { display: linking ? "none" : "flex" },
4288
+ color: item.missing ? "error.main" : item.linked ? "text.primary" : "#adb5bd",
4289
+ fontWeight: item.linked ? 700 : 400,
4290
+ "&:hover": { bgcolor: linking ? "rgba(245,130,32,.25)" : "rgba(255,255,255,.06)" }
4041
4291
  },
4042
- children: item.name || /* @__PURE__ */ jsx("em", { children: "sem nome" })
4043
- })]
4044
- }, `f:${item.uid}`))]
4045
- })]
4292
+ children: [
4293
+ /* @__PURE__ */ jsx(Box, {
4294
+ component: "span",
4295
+ sx: {
4296
+ width: 14,
4297
+ textAlign: "center",
4298
+ color: "text.secondary"
4299
+ },
4300
+ children: item.isKey ? "🔑" : item.numeric ? "Σ" : ""
4301
+ }),
4302
+ /* @__PURE__ */ jsx(Box, {
4303
+ component: "span",
4304
+ sx: {
4305
+ overflow: "hidden",
4306
+ textOverflow: "ellipsis",
4307
+ whiteSpace: "nowrap"
4308
+ },
4309
+ children: item.name
4310
+ }),
4311
+ /* @__PURE__ */ jsx(Box, {
4312
+ className: "column-actions",
4313
+ sx: {
4314
+ ml: "auto",
4315
+ gap: .25
4316
+ },
4317
+ children: columnActions.map((kind) => /* @__PURE__ */ jsx(Box, {
4318
+ component: "button",
4319
+ type: "button",
4320
+ title: kind === "dimension" ? `Nova dimensão com ${node.name}.${item.name}` : `Nova medida: ${item.numeric ? "soma" : "contagem"} de ${node.name}.${item.name}`,
4321
+ onMouseDown: (event) => event.stopPropagation(),
4322
+ onClick: () => onAddField(kind, item),
4323
+ sx: {
4324
+ border: "1px solid #3a3f47",
4325
+ borderRadius: .5,
4326
+ bgcolor: "#1b1e23",
4327
+ color: kind === "measure" ? "#ffd8a8" : "#a5d8ff",
4328
+ cursor: "pointer",
4329
+ fontSize: 10,
4330
+ lineHeight: "14px",
4331
+ px: .5
4332
+ },
4333
+ children: kind === "dimension" ? "+◇" : "+Σ"
4334
+ }, kind))
4335
+ })
4336
+ ]
4337
+ }, `c:${item.name}`) : /* @__PURE__ */ jsxs(Box, {
4338
+ onMouseDown: (event) => event.stopPropagation(),
4339
+ onClick: () => onFieldClick(item),
4340
+ sx: {
4341
+ height: ROW,
4342
+ display: "flex",
4343
+ alignItems: "center",
4344
+ gap: .75,
4345
+ px: 1,
4346
+ fontSize: 12,
4347
+ cursor: "pointer",
4348
+ color: item.kind === "measure" ? "#ffd8a8" : "#a5d8ff",
4349
+ bgcolor: selectedField === item.uid ? "rgba(245,130,32,.2)" : void 0,
4350
+ "&:hover": { bgcolor: "rgba(255,255,255,.06)" }
4351
+ },
4352
+ children: [/* @__PURE__ */ jsx(Box, {
4353
+ component: "span",
4354
+ sx: {
4355
+ width: 14,
4356
+ textAlign: "center"
4357
+ },
4358
+ children: item.icon
4359
+ }), /* @__PURE__ */ jsx(Box, {
4360
+ component: "span",
4361
+ sx: {
4362
+ overflow: "hidden",
4363
+ textOverflow: "ellipsis",
4364
+ whiteSpace: "nowrap"
4365
+ },
4366
+ children: item.name || /* @__PURE__ */ jsx("em", { children: "sem nome" })
4367
+ })]
4368
+ }, `f:${item.uid}`))]
4369
+ }),
4370
+ /* @__PURE__ */ jsx(Box, {
4371
+ onMouseDown: (event) => onResizeDown(event, {
4372
+ width: true,
4373
+ height: false
4374
+ }),
4375
+ onDoubleClick: onResizeReset,
4376
+ title: "Arraste para mudar a largura (duplo clique: tamanho automático)",
4377
+ sx: {
4378
+ ...resizeHandleSx,
4379
+ top: 0,
4380
+ right: -4,
4381
+ bottom: 8,
4382
+ width: 8,
4383
+ cursor: "ew-resize"
4384
+ }
4385
+ }),
4386
+ !collapsed && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Box, {
4387
+ onMouseDown: (event) => onResizeDown(event, {
4388
+ width: false,
4389
+ height: true
4390
+ }),
4391
+ onDoubleClick: onResizeReset,
4392
+ title: "Arraste para mudar a altura (duplo clique: tamanho automático)",
4393
+ sx: {
4394
+ ...resizeHandleSx,
4395
+ left: 0,
4396
+ right: 8,
4397
+ bottom: -4,
4398
+ height: 8,
4399
+ cursor: "ns-resize"
4400
+ }
4401
+ }), /* @__PURE__ */ jsx(Box, {
4402
+ onMouseDown: (event) => onResizeDown(event, {
4403
+ width: true,
4404
+ height: true
4405
+ }),
4406
+ onDoubleClick: onResizeReset,
4407
+ title: "Arraste para redimensionar (duplo clique: tamanho automático)",
4408
+ sx: {
4409
+ ...resizeHandleSx,
4410
+ right: -4,
4411
+ bottom: -4,
4412
+ width: 14,
4413
+ height: 14,
4414
+ cursor: "nwse-resize",
4415
+ "&::after": {
4416
+ content: "\"\"",
4417
+ position: "absolute",
4418
+ right: 6,
4419
+ bottom: 6,
4420
+ width: 6,
4421
+ height: 6,
4422
+ borderRight: "2px solid #5c636e",
4423
+ borderBottom: "2px solid #5c636e"
4424
+ },
4425
+ "&:hover::after": { borderColor: ROLE_STYLE[node.role].color }
4426
+ }
4427
+ })] })
4428
+ ]
4046
4429
  });
4047
4430
  }
4431
+ const resizeHandleSx = {
4432
+ position: "absolute",
4433
+ zIndex: 1
4434
+ };
4048
4435
  function PropertyBlock({ title, children }) {
4049
4436
  return /* @__PURE__ */ jsxs(Box, {
4050
4437
  sx: {
@@ -6883,4 +7270,4 @@ function ToggleButton$1({ draft, loading, problems, inspecting, onOpen }) {
6883
7270
  //#endregion
6884
7271
  export { DashboardDevtools as default };
6885
7272
 
6886
- //# sourceMappingURL=DashboardDevtools-C4E9afPT.js.map
7273
+ //# sourceMappingURL=DashboardDevtools-7L93aV1-.js.map