@bluemarble/bi 0.1.0 → 0.1.2

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
1
  import { C as validateModel, D as parseColumnRef, E as modelEdges, T as measureHome, _ as AGGREGATIONS, a as FILTER_FIELDS, c as chartReferences, d as layoutModelWarnings, f as validateLayout, g as toModelCode, i as CHART_TYPE_LABEL, k as referencedTables, l as filterReferences, n as CHART_FIELDS, o as chartColumns, p as canonicalJson, r as CHART_TYPES, s as chartOfType, t as CANVAS_PRESETS, u as layoutFileContent, v as AGGREGATION_LABEL, x as isAggregate, y as VALUE_FORMATS } from "./layout-iPByu6V0.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-AjNbffwG.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-DU6i_QSn.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, 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";
@@ -660,6 +660,35 @@ const inputSx$1 = { "& .MuiInputBase-input": {
660
660
  } };
661
661
  const itemElement = (layoutId, item) => document.querySelector(`[${LAYOUT_ITEM_ATTRIBUTE}="${CSS.escape(layoutItemKey(layoutId, item))}"]`);
662
662
  const itemQueryId = (layoutId, item) => itemElement(layoutId, item)?.querySelector(`[data-bm-dashboard-query]`)?.getAttribute("data-bm-dashboard-query") ?? null;
663
+ const SIDEBAR_KEY = "bm-dashboard-devtools:layout-sidebar";
664
+ /** Abaixo disso a lista vai para cima do formulário, em vez de ficar ao lado. */
665
+ const STACK_WIDTH = 640;
666
+ function loadSidebar() {
667
+ try {
668
+ return localStorage.getItem(SIDEBAR_KEY) !== "closed";
669
+ } catch {
670
+ return true;
671
+ }
672
+ }
673
+ /** Largura do elemento, acompanhando o redimensionamento do devtools. */
674
+ function useWidth() {
675
+ const [element, setElement] = useState(null);
676
+ const [width, setWidth] = useState(Number.POSITIVE_INFINITY);
677
+ useEffect(() => {
678
+ if (!element) return;
679
+ const observer = new ResizeObserver(([entry]) => entry && setWidth(entry.contentRect.width));
680
+ observer.observe(element);
681
+ return () => observer.disconnect();
682
+ }, [element]);
683
+ return [setElement, width];
684
+ }
685
+ /** Move o item `from` para antes da posição `to` (`to === list.length` = fim). */
686
+ function moveTo(list, from, to) {
687
+ const next = [...list];
688
+ const [item] = next.splice(from, 1);
689
+ next.splice(to > from ? to - 1 : to, 0, item);
690
+ return next;
691
+ }
663
692
  const uniqueId = (charts, base) => {
664
693
  const ids = new Set(charts.map((chart) => chart.id));
665
694
  if (!ids.has(base)) return base;
@@ -726,6 +755,10 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
726
755
  const reference = entry ? entry.draft ?? entry.registered : null;
727
756
  const [editing, setEditing] = useState(reference);
728
757
  const [selection, setSelection] = useState(null);
758
+ const [sidebar, setSidebar] = useState(loadSidebar);
759
+ const [drag, setDrag] = useState(null);
760
+ const [rootRef, rootWidth] = useWidth();
761
+ const [editorRef, editorWidth] = useWidth();
729
762
  const [busy, setBusy] = useState(false);
730
763
  const [actionError, setActionError] = useState(null);
731
764
  const [save, setSave] = useState(null);
@@ -768,7 +801,7 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
768
801
  const charts = editing.charts;
769
802
  const filters = editing.filters ?? [];
770
803
  const selectedFilter = selection?.kind === "filter" ? filters.find((filter) => filter.dimension === selection.key) ?? null : null;
771
- const selected = selectedFilter || selection?.kind === "filter" ? null : charts.find((chart) => chart.id === selection?.key) ?? charts[0] ?? null;
804
+ const selected = selection?.kind === "filter" || selection?.kind === "canvas" ? null : charts.find((chart) => chart.id === selection?.key) ?? charts[0] ?? null;
772
805
  const layoutKey = entry.registered.id;
773
806
  const differsFromFile = canonicalJson(editing) !== canonicalJson(entry.registered);
774
807
  const update = (next) => {
@@ -790,21 +823,39 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
790
823
  filters: next
791
824
  });
792
825
  const patchFilter = (dimension, change) => setFilters(filters.map((filter) => filter.dimension === dimension ? change(filter) : filter));
793
- const swap = (list, index, delta) => {
794
- const target = index + delta;
795
- if (target < 0 || target >= list.length) return null;
796
- const next = [...list];
797
- [next[index], next[target]] = [next[target], next[index]];
798
- return next;
799
- };
800
- const move = (index, delta) => {
801
- const next = swap(charts, index, delta);
802
- if (next) setCharts(next);
803
- };
804
- const moveFilter = (index, delta) => {
805
- const next = swap(filters, index, delta);
806
- if (next) setFilters(next);
826
+ const toggleSidebar = () => {
827
+ const next = !sidebar;
828
+ setSidebar(next);
829
+ try {
830
+ localStorage.setItem(SIDEBAR_KEY, next ? "open" : "closed");
831
+ } catch {}
807
832
  };
833
+ const dragProps = (kind, index, length) => ({
834
+ dropBefore: drag?.kind === kind && drag.from !== index && drag.to === index,
835
+ dropAfter: drag?.kind === kind && index === length - 1 && drag.to === length,
836
+ onDragStart: () => setDrag({
837
+ kind,
838
+ from: index,
839
+ to: index
840
+ }),
841
+ onDragOver: (e) => {
842
+ if (drag?.kind !== kind) return;
843
+ e.preventDefault();
844
+ const rect = e.currentTarget.getBoundingClientRect();
845
+ const to = e.clientY < rect.top + rect.height / 2 ? index : index + 1;
846
+ if (to !== drag.to) setDrag({
847
+ ...drag,
848
+ to
849
+ });
850
+ },
851
+ onDrop: () => {
852
+ if (drag?.kind !== kind) return;
853
+ if (drag.to !== drag.from && drag.to !== drag.from + 1) if (kind === "chart") setCharts(moveTo(charts, drag.from, drag.to));
854
+ else setFilters(moveTo(filters, drag.from, drag.to));
855
+ setDrag(null);
856
+ },
857
+ onDragEnd: () => setDrag(null)
858
+ });
808
859
  const addFilter = () => {
809
860
  const used = new Set(filters.map((filter) => filter.dimension));
810
861
  const dimension = Object.keys(model.dimensions).find((name) => !used.has(name));
@@ -817,11 +868,15 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
817
868
  };
818
869
  const focus = (item) => {
819
870
  setSelection(item);
871
+ if (stacked) setSidebar(false);
820
872
  itemElement(layoutKey, item)?.scrollIntoView({
821
873
  behavior: "smooth",
822
874
  block: "center"
823
875
  });
824
876
  };
877
+ const stacked = rootWidth < STACK_WIDTH;
878
+ const formColumns = editorWidth < 420 ? 1 : editorWidth < 720 ? 2 : 4;
879
+ const selectedTitle = selection?.kind === "canvas" ? "Tela" : selectedFilter ? selectedFilter.label || model.dimensions[selectedFilter.dimension]?.label || selectedFilter.dimension : selected ? selected.title || selected.id : "";
825
880
  const hover = (item) => onHover(item && itemQueryId(layoutKey, item));
826
881
  const add = (type, base) => {
827
882
  setAddMenu(null);
@@ -864,6 +919,7 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
864
919
  }
865
920
  };
866
921
  return /* @__PURE__ */ jsxs(Box, {
922
+ ref: rootRef,
867
923
  sx: {
868
924
  display: "flex",
869
925
  flexDirection: "column",
@@ -871,19 +927,45 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
871
927
  minHeight: 0
872
928
  },
873
929
  children: [
930
+ /* @__PURE__ */ jsxs(Box, {
931
+ sx: {
932
+ display: "flex",
933
+ alignItems: "center",
934
+ gap: 1,
935
+ px: 1,
936
+ py: .5,
937
+ borderBottom: 1,
938
+ borderColor: "divider"
939
+ },
940
+ children: [/* @__PURE__ */ jsx(SmallButton, {
941
+ title: sidebar ? "Recolher a lista" : "Mostrar a lista",
942
+ onClick: toggleSidebar,
943
+ children: sidebar ? "«" : "☰"
944
+ }), /* @__PURE__ */ jsx(Typography, {
945
+ noWrap: true,
946
+ sx: {
947
+ fontSize: 12,
948
+ fontWeight: 700
949
+ },
950
+ children: stacked && sidebar ? `Layout ${layoutKey}` : selectedTitle
951
+ })]
952
+ }),
874
953
  /* @__PURE__ */ jsxs(Box, {
875
954
  sx: {
876
955
  flex: 1,
877
956
  minHeight: 0,
878
- display: "grid",
879
- gridTemplateColumns: "280px 1fr"
957
+ display: "flex"
880
958
  },
881
- children: [/* @__PURE__ */ jsxs(Box, {
959
+ children: [sidebar && /* @__PURE__ */ jsxs(Box, {
882
960
  sx: {
883
- borderRight: 1,
884
- borderColor: "divider",
885
961
  overflow: "auto",
886
- p: 1.5
962
+ p: 1.5,
963
+ ...stacked ? { flex: 1 } : {
964
+ width: 280,
965
+ flexShrink: 0,
966
+ borderRight: 1,
967
+ borderColor: "divider"
968
+ }
887
969
  },
888
970
  children: [
889
971
  entries.length > 1 && /* @__PURE__ */ jsx(TextField, {
@@ -902,9 +984,17 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
902
984
  children: [item.registered.id, item.draft ? " · rascunho" : ""]
903
985
  }, item.registered.id))
904
986
  }),
905
- /* @__PURE__ */ jsx(CanvasSection, {
906
- canvas: editing.canvas,
907
- onChange: setCanvas
987
+ /* @__PURE__ */ jsx(Section, {
988
+ title: "Tela",
989
+ children: /* @__PURE__ */ jsx(ItemRow, {
990
+ title: editing.canvas ? "Tamanho fixo" : "Responsiva",
991
+ subtitle: editing.canvas ? presetLabel(editing.canvas) : "reorganiza conforme a largura",
992
+ selected: selection?.kind === "canvas",
993
+ onSelect: () => {
994
+ setSelection({ kind: "canvas" });
995
+ if (stacked) setSidebar(false);
996
+ }
997
+ })
908
998
  }),
909
999
  /* @__PURE__ */ jsxs(Section, {
910
1000
  title: `Filtros (${filters.length})`,
@@ -932,8 +1022,7 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
932
1022
  selected: selectedFilter?.dimension === filter.dimension,
933
1023
  onSelect: () => focus(item),
934
1024
  onHover: (on) => hover(on ? item : null),
935
- onUp: index ? () => moveFilter(index, -1) : void 0,
936
- onDown: index < filters.length - 1 ? () => moveFilter(index, 1) : void 0,
1025
+ ...dragProps("filter", index, filters.length),
937
1026
  onRemove: () => setFilters(filters.filter((other) => other.dimension !== filter.dimension))
938
1027
  }, filter.dimension);
939
1028
  })]
@@ -965,79 +1054,90 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
965
1054
  selected: chart.id === selected?.id,
966
1055
  onSelect: () => focus(item),
967
1056
  onHover: (on) => hover(on ? item : null),
968
- onUp: index ? () => move(index, -1) : void 0,
969
- onDown: index < charts.length - 1 ? () => move(index, 1) : void 0,
1057
+ ...dragProps("chart", index, charts.length),
970
1058
  onDuplicate: () => add(chart.type, chart),
971
1059
  onRemove: () => setCharts(charts.filter((other) => other.id !== chart.id))
972
1060
  }, chart.id);
973
1061
  })
974
1062
  })
975
1063
  ]
976
- }), /* @__PURE__ */ jsxs(Box, {
1064
+ }), !(stacked && sidebar) && /* @__PURE__ */ jsx(Box, {
1065
+ ref: editorRef,
977
1066
  sx: {
978
- overflow: "auto",
979
- p: 1.5
1067
+ flex: 1,
1068
+ minWidth: 0,
1069
+ overflow: "auto"
980
1070
  },
981
- children: [
982
- entry.draft && /* @__PURE__ */ jsxs(Alert, {
983
- severity: "warning",
984
- sx: { mb: 1.5 },
985
- children: [
986
- "Rascunho aplicado: a página mostra o layout editado, não o arquivo",
987
- " ",
988
- /* @__PURE__ */ jsxs("code", { children: [entry.registered.id, ".json"] }),
989
- ".",
990
- capabilities?.saveLayout ? " Use \"Salvar no arquivo\" quando terminar." : " Copie o JSON para o arquivo quando terminar."
991
- ]
992
- }),
993
- !!errors.length && /* @__PURE__ */ jsxs(Alert, {
994
- severity: "error",
995
- sx: { mb: 1.5 },
996
- children: ["Layout inválido — as edições não foram aplicadas na página:", errors.map((error) => /* @__PURE__ */ jsx("div", { children: error }, error))]
997
- }),
998
- !!warnings.length && /* @__PURE__ */ jsx(Alert, {
999
- severity: "warning",
1000
- sx: { mb: 1.5 },
1001
- children: warnings.map((warning) => /* @__PURE__ */ jsx("div", { children: warning }, warning))
1002
- }),
1003
- actionError && /* @__PURE__ */ jsx(Alert, {
1004
- severity: "error",
1005
- sx: {
1006
- mb: 1.5,
1007
- fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
1008
- fontSize: 12
1009
- },
1010
- children: actionError
1011
- }),
1012
- selectedFilter ? /* @__PURE__ */ jsx(FilterForm, {
1013
- filter: selectedFilter,
1014
- model,
1015
- formatters: entry.parts.filters ?? [],
1016
- onChange: (change) => {
1017
- patchFilter(selectedFilter.dimension, change);
1018
- setSelection({
1019
- kind: "filter",
1020
- key: change(selectedFilter).dimension
1021
- });
1022
- }
1023
- }, `filter:${selectedFilter.dimension}`) : selected ? /* @__PURE__ */ jsx(ChartForm, {
1024
- chart: selected,
1025
- model,
1026
- formatters: entry.parts.charts ?? [],
1027
- onChange: (change) => {
1028
- patchChart(selected.id, change);
1029
- setSelection({
1030
- kind: "chart",
1031
- key: change(selected).id
1032
- });
1033
- }
1034
- }, `chart:${selected.id}`) : /* @__PURE__ */ jsx(Empty, { children: "Escolha um filtro ou gráfico, ou use \"+ Novo\"." })
1035
- ]
1071
+ children: /* @__PURE__ */ jsxs(Box, {
1072
+ sx: { p: 1.5 },
1073
+ children: [
1074
+ entry.draft && /* @__PURE__ */ jsxs(Alert, {
1075
+ severity: "warning",
1076
+ sx: { mb: 1.5 },
1077
+ children: [
1078
+ "Rascunho aplicado: a página mostra o layout editado, não o arquivo",
1079
+ " ",
1080
+ /* @__PURE__ */ jsxs("code", { children: [entry.registered.id, ".json"] }),
1081
+ ".",
1082
+ capabilities?.saveLayout ? " Use \"Salvar no arquivo\" quando terminar." : " Copie o JSON para o arquivo quando terminar."
1083
+ ]
1084
+ }),
1085
+ !!errors.length && /* @__PURE__ */ jsxs(Alert, {
1086
+ severity: "error",
1087
+ sx: { mb: 1.5 },
1088
+ children: ["Layout inválido — as edições não foram aplicadas na página:", errors.map((error) => /* @__PURE__ */ jsx("div", { children: error }, error))]
1089
+ }),
1090
+ !!warnings.length && /* @__PURE__ */ jsx(Alert, {
1091
+ severity: "warning",
1092
+ sx: { mb: 1.5 },
1093
+ children: warnings.map((warning) => /* @__PURE__ */ jsx("div", { children: warning }, warning))
1094
+ }),
1095
+ actionError && /* @__PURE__ */ jsx(Alert, {
1096
+ severity: "error",
1097
+ sx: {
1098
+ mb: 1.5,
1099
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
1100
+ fontSize: 12
1101
+ },
1102
+ children: actionError
1103
+ }),
1104
+ selection?.kind === "canvas" ? /* @__PURE__ */ jsx(CanvasForm, {
1105
+ canvas: editing.canvas,
1106
+ columns: formColumns,
1107
+ onChange: setCanvas
1108
+ }) : selectedFilter ? /* @__PURE__ */ jsx(FilterForm, {
1109
+ filter: selectedFilter,
1110
+ model,
1111
+ formatters: entry.parts.filters ?? [],
1112
+ columns: formColumns,
1113
+ onChange: (change) => {
1114
+ patchFilter(selectedFilter.dimension, change);
1115
+ setSelection({
1116
+ kind: "filter",
1117
+ key: change(selectedFilter).dimension
1118
+ });
1119
+ }
1120
+ }, `filter:${selectedFilter.dimension}`) : selected ? /* @__PURE__ */ jsx(ChartForm, {
1121
+ chart: selected,
1122
+ model,
1123
+ formatters: entry.parts.charts ?? [],
1124
+ columns: formColumns,
1125
+ onChange: (change) => {
1126
+ patchChart(selected.id, change);
1127
+ setSelection({
1128
+ kind: "chart",
1129
+ key: change(selected).id
1130
+ });
1131
+ }
1132
+ }, `chart:${selected.id}`) : /* @__PURE__ */ jsx(Empty, { children: "Escolha a tela, um filtro ou um gráfico, ou use \"+ Novo\"." })
1133
+ ]
1134
+ })
1036
1135
  })]
1037
1136
  }),
1038
1137
  /* @__PURE__ */ jsxs(Box, {
1039
1138
  sx: {
1040
1139
  display: "flex",
1140
+ flexWrap: "wrap",
1041
1141
  gap: 1,
1042
1142
  justifyContent: "flex-end",
1043
1143
  alignItems: "center",
@@ -1120,74 +1220,88 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
1120
1220
  }
1121
1221
  const presetLabel = ({ width, height }) => `${width}×${height ?? "auto"}`;
1122
1222
  /** Tamanho fixo da tela (`layout.canvas`): desligado = responsivo. */
1123
- function CanvasSection({ canvas, onChange }) {
1223
+ function CanvasForm({ canvas, columns, onChange }) {
1124
1224
  const preset = canvas && CANVAS_PRESETS.find((item) => presetLabel(item) === presetLabel(canvas));
1125
1225
  const number = (value) => value === "" ? void 0 : Number(value);
1126
- return /* @__PURE__ */ jsxs(Section, {
1226
+ return /* @__PURE__ */ jsx(Section, {
1127
1227
  title: "Tela",
1128
- children: [/* @__PURE__ */ jsx(Tooltip, {
1129
- title: "Desenha o dashboard nesse tamanho e escala para caber na tela, mantendo a proporção, em vez de reorganizar os gráficos. A página precisa do <DashboardCanvas>.",
1130
- placement: "right",
1131
- children: /* @__PURE__ */ jsx(FormControlLabel, {
1132
- control: /* @__PURE__ */ jsx(Checkbox, {
1133
- size: "small",
1134
- checked: !!canvas,
1135
- onChange: (e) => onChange(e.target.checked ? CANVAS_PRESETS[0] : void 0)
1136
- }),
1137
- label: /* @__PURE__ */ jsx(Typography, {
1138
- sx: { fontSize: 12 },
1139
- children: "Tamanho fixo"
1140
- })
1141
- })
1142
- }), canvas && /* @__PURE__ */ jsxs(Box, {
1143
- sx: {
1144
- display: "grid",
1145
- gridTemplateColumns: "1fr 1fr",
1146
- gap: 1,
1147
- mt: .5
1148
- },
1149
- children: [
1150
- /* @__PURE__ */ jsx(TextField, {
1228
+ children: /* @__PURE__ */ jsxs(Box, {
1229
+ sx: formGrid(columns),
1230
+ children: [/* @__PURE__ */ jsx(Box, {
1231
+ sx: { gridColumn: "1 / -1" },
1232
+ children: withHint(/* @__PURE__ */ jsx(FormControlLabel, {
1233
+ control: /* @__PURE__ */ jsx(Checkbox, {
1234
+ size: "small",
1235
+ checked: !!canvas,
1236
+ onChange: (e) => onChange(e.target.checked ? CANVAS_PRESETS[0] : void 0)
1237
+ }),
1238
+ label: /* @__PURE__ */ jsx(Typography, {
1239
+ sx: { fontSize: 12 },
1240
+ children: "Tamanho fixo"
1241
+ })
1242
+ }), "Desenha o dashboard nesse tamanho e escala para caber na tela, mantendo a proporção, em vez de reorganizar os gráficos. A página precisa do <DashboardCanvas>.")
1243
+ }), canvas && /* @__PURE__ */ jsxs(Fragment, { children: [
1244
+ field("Predefinido", /* @__PURE__ */ jsx(TextField, {
1151
1245
  select: true,
1152
1246
  size: "small",
1153
- label: "Predefinido",
1247
+ fullWidth: true,
1154
1248
  value: preset ? presetLabel(preset) : "",
1155
1249
  onChange: (e) => onChange(CANVAS_PRESETS.find((item) => presetLabel(item) === e.target.value)),
1156
- sx: {
1157
- ...inputSx$1,
1158
- gridColumn: "1 / -1"
1159
- },
1250
+ sx: inputSx$1,
1160
1251
  children: CANVAS_PRESETS.map((item) => /* @__PURE__ */ jsx(MenuItem, {
1161
1252
  value: presetLabel(item),
1162
1253
  children: presetLabel(item)
1163
1254
  }, presetLabel(item)))
1164
- }),
1165
- /* @__PURE__ */ jsx(TextField, {
1255
+ })),
1256
+ field("Largura", /* @__PURE__ */ jsx(TextField, {
1166
1257
  size: "small",
1258
+ fullWidth: true,
1167
1259
  type: "number",
1168
- label: "Largura",
1169
1260
  value: canvas.width ?? "",
1170
1261
  onChange: (e) => onChange(withField(canvas, "width", number(e.target.value))),
1171
1262
  sx: inputSx$1
1172
- }),
1173
- /* @__PURE__ */ jsx(TextField, {
1263
+ })),
1264
+ field("Altura mín.", /* @__PURE__ */ jsx(TextField, {
1174
1265
  size: "small",
1266
+ fullWidth: true,
1175
1267
  type: "number",
1176
- label: "Altura mín.",
1177
1268
  value: canvas.height ?? "",
1269
+ placeholder: "auto",
1178
1270
  onChange: (e) => onChange(withField(canvas, "height", number(e.target.value))),
1179
1271
  sx: inputSx$1
1180
- })
1181
- ]
1182
- })]
1272
+ }))
1273
+ ] })]
1274
+ })
1183
1275
  });
1184
1276
  }
1185
- function ItemRow({ title, subtitle, selected, onSelect, onHover, onUp, onDown, onDuplicate, onRemove }) {
1277
+ function ItemRow({ title, subtitle, selected, onSelect, onHover, onDuplicate, onRemove, dropBefore, dropAfter, onDragStart, onDragOver, onDrop, onDragEnd }) {
1278
+ const marker = {
1279
+ content: "\"\"",
1280
+ position: "absolute",
1281
+ left: 0,
1282
+ right: 0,
1283
+ height: 2,
1284
+ bgcolor: "primary.main"
1285
+ };
1186
1286
  return /* @__PURE__ */ jsxs(Box, {
1187
1287
  onClick: onSelect,
1188
- onMouseEnter: () => onHover(true),
1189
- onMouseLeave: () => onHover(false),
1288
+ onMouseEnter: () => onHover?.(true),
1289
+ onMouseLeave: () => onHover?.(false),
1290
+ draggable: !!onDragStart,
1291
+ onDragStart: (e) => {
1292
+ if (!onDragStart) return;
1293
+ e.dataTransfer.effectAllowed = "move";
1294
+ e.dataTransfer.setData("text/plain", title);
1295
+ onDragStart();
1296
+ },
1297
+ onDragOver,
1298
+ onDrop: (e) => {
1299
+ e.preventDefault();
1300
+ onDrop?.();
1301
+ },
1302
+ onDragEnd,
1190
1303
  sx: {
1304
+ position: "relative",
1191
1305
  display: "flex",
1192
1306
  alignItems: "center",
1193
1307
  gap: .5,
@@ -1195,64 +1309,71 @@ function ItemRow({ title, subtitle, selected, onSelect, onHover, onUp, onDown, o
1195
1309
  py: .5,
1196
1310
  mb: .5,
1197
1311
  borderRadius: 1,
1198
- cursor: "pointer",
1312
+ cursor: onDragStart ? "grab" : "pointer",
1199
1313
  border: 1,
1200
1314
  borderColor: selected ? "primary.main" : "divider",
1201
- "&:hover .item-actions": { visibility: "visible" }
1315
+ "&:hover .item-actions": { visibility: "visible" },
1316
+ "&::before": dropBefore ? {
1317
+ ...marker,
1318
+ top: -4
1319
+ } : void 0,
1320
+ "&::after": dropAfter ? {
1321
+ ...marker,
1322
+ bottom: -4
1323
+ } : void 0
1202
1324
  },
1203
- children: [/* @__PURE__ */ jsxs(Box, {
1204
- sx: {
1205
- minWidth: 0,
1206
- flex: 1
1207
- },
1208
- children: [/* @__PURE__ */ jsx(Typography, {
1209
- noWrap: true,
1325
+ children: [
1326
+ onDragStart && /* @__PURE__ */ jsx(Box, {
1327
+ component: "span",
1328
+ "aria-hidden": true,
1210
1329
  sx: {
1330
+ color: "text.disabled",
1211
1331
  fontSize: 12,
1212
- fontWeight: 700
1332
+ lineHeight: 1,
1333
+ userSelect: "none"
1213
1334
  },
1214
- children: title
1215
- }), /* @__PURE__ */ jsx(Typography, {
1216
- noWrap: true,
1335
+ children: "⋮⋮"
1336
+ }),
1337
+ /* @__PURE__ */ jsxs(Box, {
1217
1338
  sx: {
1218
- fontSize: 11,
1219
- color: "text.secondary",
1220
- fontFamily: MONO
1339
+ minWidth: 0,
1340
+ flex: 1
1221
1341
  },
1222
- children: subtitle
1223
- })]
1224
- }), /* @__PURE__ */ jsxs(Box, {
1225
- className: "item-actions",
1226
- onClick: (e) => e.stopPropagation(),
1227
- sx: {
1228
- display: "flex",
1229
- visibility: "hidden"
1230
- },
1231
- children: [
1232
- /* @__PURE__ */ jsx(SmallButton, {
1233
- title: "Subir",
1234
- disabled: !onUp,
1235
- onClick: () => onUp?.(),
1236
- children: "↑"
1237
- }),
1238
- /* @__PURE__ */ jsx(SmallButton, {
1239
- title: "Descer",
1240
- disabled: !onDown,
1241
- onClick: () => onDown?.(),
1242
- children: "↓"
1243
- }),
1244
- onDuplicate && /* @__PURE__ */ jsx(SmallButton, {
1342
+ children: [/* @__PURE__ */ jsx(Typography, {
1343
+ noWrap: true,
1344
+ sx: {
1345
+ fontSize: 12,
1346
+ fontWeight: 700
1347
+ },
1348
+ children: title
1349
+ }), /* @__PURE__ */ jsx(Typography, {
1350
+ noWrap: true,
1351
+ sx: {
1352
+ fontSize: 11,
1353
+ color: "text.secondary",
1354
+ fontFamily: MONO
1355
+ },
1356
+ children: subtitle
1357
+ })]
1358
+ }),
1359
+ (onDuplicate || onRemove) && /* @__PURE__ */ jsxs(Box, {
1360
+ className: "item-actions",
1361
+ onClick: (e) => e.stopPropagation(),
1362
+ sx: {
1363
+ display: "flex",
1364
+ visibility: selected ? "visible" : "hidden"
1365
+ },
1366
+ children: [onDuplicate && /* @__PURE__ */ jsx(SmallButton, {
1245
1367
  title: "Duplicar",
1246
1368
  onClick: onDuplicate,
1247
1369
  children: "⧉"
1248
- }),
1249
- /* @__PURE__ */ jsx(SmallButton, {
1370
+ }), onRemove && /* @__PURE__ */ jsx(SmallButton, {
1250
1371
  title: "Remover",
1251
1372
  onClick: onRemove,
1252
1373
  children: "×"
1253
- })
1254
- ]
1255
- })]
1374
+ })]
1375
+ })
1376
+ ]
1256
1377
  });
1257
1378
  }
1258
1379
  function SmallButton({ title, disabled, onClick, children }) {
@@ -1271,7 +1392,7 @@ function SmallButton({ title, disabled, onClick, children }) {
1271
1392
  }) })
1272
1393
  });
1273
1394
  }
1274
- function ChartForm({ chart, model, formatters, onChange }) {
1395
+ function ChartForm({ chart, model, formatters, columns, onChange }) {
1275
1396
  const set = (key, value) => onChange((current) => withField(current, key, value));
1276
1397
  return /* @__PURE__ */ jsx(FieldsForm, {
1277
1398
  fields: CHART_FIELDS[chart.type],
@@ -1280,6 +1401,7 @@ function ChartForm({ chart, model, formatters, onChange }) {
1280
1401
  model,
1281
1402
  formatters,
1282
1403
  columns: chartColumns(chart),
1404
+ formColumns: columns,
1283
1405
  chosenDimensions: chartReferences(chart).dimensions,
1284
1406
  header: /* @__PURE__ */ jsxs(Fragment, { children: [field("Tipo", /* @__PURE__ */ jsx(TextField, {
1285
1407
  select: true,
@@ -1302,7 +1424,7 @@ function ChartForm({ chart, model, formatters, onChange }) {
1302
1424
  }), "Identificador no JSON (letras, números, \"-\" e \"_\")")] })
1303
1425
  });
1304
1426
  }
1305
- function FilterForm({ filter, model, formatters, onChange }) {
1427
+ function FilterForm({ filter, model, formatters, columns, onChange }) {
1306
1428
  return /* @__PURE__ */ jsx(FieldsForm, {
1307
1429
  fields: FILTER_FIELDS,
1308
1430
  values: filter,
@@ -1310,11 +1432,12 @@ function FilterForm({ filter, model, formatters, onChange }) {
1310
1432
  model,
1311
1433
  formatters,
1312
1434
  columns: [],
1435
+ formColumns: columns,
1313
1436
  chosenDimensions: [filter.dimension]
1314
1437
  });
1315
1438
  }
1316
1439
  /** Formulário gerado dos `FieldSpec` (campos do tipo de gráfico ou do filtro). */
1317
- function FieldsForm({ fields, values, onSet: set, model, formatters, columns, chosenDimensions, header }) {
1440
+ function FieldsForm({ fields, values, onSet: set, model, formatters, columns, formColumns, chosenDimensions, header }) {
1318
1441
  const dimensions = Object.entries(model.dimensions).map(([name, dim]) => ({
1319
1442
  name,
1320
1443
  label: dim.label
@@ -1484,21 +1607,16 @@ function FieldsForm({ fields, values, onSet: set, model, formatters, columns, ch
1484
1607
  }
1485
1608
  };
1486
1609
  const sections = [...new Set(entries.map(([, spec]) => spec.section))];
1487
- const grid = {
1488
- display: "grid",
1489
- gap: 1.5,
1490
- gridTemplateColumns: "repeat(4, minmax(0, 1fr))"
1491
- };
1492
1610
  const wide = (spec, key) => key === "title" || spec.kind === "dimensions" || spec.kind === "measures" || spec.kind === "formatters";
1493
1611
  return /* @__PURE__ */ jsx(Fragment, { children: sections.map((section) => {
1494
1612
  const inSection = entries.filter(([, spec]) => spec.section === section);
1495
1613
  return /* @__PURE__ */ jsx(Section, {
1496
1614
  title: section,
1497
1615
  children: /* @__PURE__ */ jsxs(Box, {
1498
- sx: grid,
1616
+ sx: formGrid(formColumns),
1499
1617
  children: [section === sections[0] && header, inSection.map(([key, spec]) => /* @__PURE__ */ jsx(Box, {
1500
1618
  sx: {
1501
- gridColumn: wide(spec, key) ? "span 2" : void 0,
1619
+ gridColumn: wide(spec, key) && formColumns > 1 ? "span 2" : void 0,
1502
1620
  alignSelf: spec.kind === "boolean" ? "end" : void 0
1503
1621
  },
1504
1622
  children: spec.kind === "boolean" ? withHint(input(key, spec), spec.hint) : field(spec.label, input(key, spec), spec.hint)
@@ -1507,6 +1625,11 @@ function FieldsForm({ fields, values, onSet: set, model, formatters, columns, ch
1507
1625
  }, section);
1508
1626
  }) });
1509
1627
  }
1628
+ const formGrid = (columns) => ({
1629
+ display: "grid",
1630
+ gap: 1.5,
1631
+ gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`
1632
+ });
1510
1633
  /** Rótulo padrão (do dicionário) como placeholder dos campos `*Label` do combo. */
1511
1634
  function labelPlaceholder(key, values, model) {
1512
1635
  if (key === "label" && typeof values.dimension === "string") return model.dimensions[values.dimension]?.label ?? values.dimension;
@@ -6258,4 +6381,4 @@ function ToggleButton$1({ draft, loading, problems, inspecting, onOpen }) {
6258
6381
  //#endregion
6259
6382
  export { DashboardDevtools as default };
6260
6383
 
6261
- //# sourceMappingURL=DashboardDevtools-ByrFb6HT.js.map
6384
+ //# sourceMappingURL=DashboardDevtools-DLhEr3fN.js.map