@bluemarble/bi 0.2.0 → 0.3.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,9 +1,16 @@
1
- import { A as referencedTables, D as modelEdges, E as measureHome, O as parseColumnRef, S as isAggregate, _ as toModelCode, a as chartOfType, b as VALUE_FORMATS, c as filterReferences, d as layoutModelWarnings, i as chartColumns, m as canonicalJson, n as CANVAS_PRESETS, o as chartReferences, p as validateLayout, r as FILTER_FIELDS, t as BUILTIN_CHART_TYPES, u as layoutFileContent, v as AGGREGATIONS, w as validateModel, y as AGGREGATION_LABEL } from "./layout-DvHS8XFz.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-DTR4vI-I.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-DN77UDVM.js";
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";
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-CBILYbZ1.js";
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";
7
+ //#region \0rolldown/runtime.js
8
+ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
9
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) {
10
+ if (typeof require !== "undefined") return require.apply(this, arguments);
11
+ throw Error("Calling `require` for \"" + x + "\" in an environment that doesn't expose the `require` function. See https://rolldown.rs/in-depth/bundling-cjs#require-external-modules for more details.");
12
+ });
13
+ //#endregion
7
14
  //#region src/devtools/sqlHighlight.tsx
8
15
  const KEYWORDS$1 = new Set(`SELECT FROM WHERE AND OR NOT IN IS NULL JOIN LEFT RIGHT INNER OUTER CROSS ON AS GROUP BY ORDER
9
16
  HAVING LIMIT OFFSET UNION ALL DISTINCT CASE WHEN THEN ELSE END WITH ASC DESC BETWEEN LIKE EXISTS
@@ -91,9 +98,13 @@ function StatusDot({ status }) {
91
98
  }
92
99
  });
93
100
  }
94
- async function copyText(text) {
101
+ /**
102
+ * Copia para a área de transferência. Com o devtools em janela separada, o clipboard tem que ser o
103
+ * dela (o da página não está em foco e recusa): passe o elemento clicado.
104
+ */
105
+ async function copyText(text, from) {
95
106
  try {
96
- await navigator.clipboard.writeText(text);
107
+ await (from?.ownerDocument.defaultView ?? window).navigator.clipboard.writeText(text);
97
108
  return true;
98
109
  } catch {
99
110
  return false;
@@ -103,8 +114,8 @@ function CopyButton({ text, label = "Copiar" }) {
103
114
  const [copied, setCopied] = useState(false);
104
115
  return /* @__PURE__ */ jsx(Button, {
105
116
  size: "small",
106
- onClick: async () => {
107
- if (await copyText(text)) {
117
+ onClick: async (event) => {
118
+ if (await copyText(text, event.currentTarget)) {
108
119
  setCopied(true);
109
120
  setTimeout(() => setCopied(false), 1200);
110
121
  }
@@ -112,9 +123,33 @@ function CopyButton({ text, label = "Copiar" }) {
112
123
  children: copied ? "Copiado!" : label
113
124
  });
114
125
  }
115
- function Section({ title, actions, children }) {
126
+ const COLLAPSED_KEY = "bm-dashboard-devtools:collapsed";
127
+ function loadCollapsed() {
128
+ try {
129
+ const saved = JSON.parse(localStorage.getItem(COLLAPSED_KEY) ?? "[]");
130
+ return Array.isArray(saved) ? saved : [];
131
+ } catch {
132
+ return [];
133
+ }
134
+ }
135
+ /** Seção recolhida (lembrada entre recargas, pela chave). */
136
+ function useCollapsed(key) {
137
+ const [collapsed, setCollapsed] = useState(() => !!key && loadCollapsed().includes(key));
138
+ const toggle = () => {
139
+ if (!key) return;
140
+ const next = !collapsed;
141
+ setCollapsed(next);
142
+ try {
143
+ const others = loadCollapsed().filter((item) => item !== key);
144
+ localStorage.setItem(COLLAPSED_KEY, JSON.stringify(next ? [...others, key] : others));
145
+ } catch {}
146
+ };
147
+ return [collapsed, toggle];
148
+ }
149
+ function Section({ title, actions, collapseKey, children }) {
150
+ const [collapsed, toggle] = useCollapsed(collapseKey);
116
151
  return /* @__PURE__ */ jsxs(Box, {
117
- sx: { mb: 2 },
152
+ sx: { mb: collapsed ? .5 : 2 },
118
153
  children: [/* @__PURE__ */ jsxs(Box, {
119
154
  sx: {
120
155
  display: "flex",
@@ -123,15 +158,43 @@ function Section({ title, actions, children }) {
123
158
  mb: .5,
124
159
  minHeight: 30
125
160
  },
126
- children: [/* @__PURE__ */ jsx(Typography, {
161
+ children: [/* @__PURE__ */ jsxs(Typography, {
127
162
  variant: "overline",
163
+ component: collapseKey ? "button" : "span",
164
+ ...collapseKey && {
165
+ type: "button",
166
+ onClick: toggle,
167
+ "aria-expanded": !collapsed,
168
+ title: collapsed ? "Expandir" : "Recolher"
169
+ },
128
170
  sx: {
129
171
  color: "text.secondary",
130
172
  lineHeight: 1.5,
131
- fontWeight: 700
173
+ fontWeight: 700,
174
+ ...collapseKey && {
175
+ display: "flex",
176
+ alignItems: "center",
177
+ gap: .5,
178
+ p: 0,
179
+ border: 0,
180
+ bgcolor: "transparent",
181
+ cursor: "pointer",
182
+ textAlign: "left",
183
+ "&:hover": { color: "text.primary" }
184
+ }
132
185
  },
133
- children: title
134
- }), /* @__PURE__ */ jsx(Box, {
186
+ children: [collapseKey && /* @__PURE__ */ jsx(Box, {
187
+ component: "span",
188
+ "aria-hidden": true,
189
+ sx: {
190
+ display: "inline-block",
191
+ fontSize: 9,
192
+ transition: "transform .15s",
193
+ transform: collapsed ? "rotate(-90deg)" : "none"
194
+ },
195
+ children: "▼"
196
+ }), title]
197
+ }), !collapsed && /* @__PURE__ */ jsx(Box, {
135
198
  sx: {
136
199
  ml: "auto",
137
200
  display: "flex",
@@ -139,7 +202,7 @@ function Section({ title, actions, children }) {
139
202
  },
140
203
  children: actions
141
204
  })]
142
- }), children]
205
+ }), !collapsed && children]
143
206
  });
144
207
  }
145
208
  function CodeBlock({ code, maxHeight = 260, sql = false }) {
@@ -181,7 +244,16 @@ function inlineParams(sql, params) {
181
244
  });
182
245
  }
183
246
  const MAX_ROWS = 500;
247
+ /** Ordena linhas de resultado por uma coluna; `null` sempre no fim. */
248
+ function compareCells(a, b, { column, direction }) {
249
+ const [x, y] = [a[column], b[column]];
250
+ if (x === null || x === void 0) return y === null || y === void 0 ? 0 : 1;
251
+ if (y === null || y === void 0) return -1;
252
+ const order = typeof x === "number" && typeof y === "number" ? x - y : String(x).localeCompare(String(y), "pt-BR", { numeric: true });
253
+ return direction === "asc" ? order : -order;
254
+ }
184
255
  function ResultTable({ rows, maxHeight = 320 }) {
256
+ const [sort, setSort] = useState(null);
185
257
  if (!rows.length) return /* @__PURE__ */ jsx(Typography, {
186
258
  sx: {
187
259
  color: "text.secondary",
@@ -190,6 +262,14 @@ function ResultTable({ rows, maxHeight = 320 }) {
190
262
  children: "Nenhuma linha."
191
263
  });
192
264
  const columns = Object.keys(rows[0]);
265
+ const sorted = sort ? [...rows].sort((a, b) => compareCells(a, b, sort)) : rows;
266
+ const toggleSort = (column) => setSort((current) => current?.column !== column ? {
267
+ column,
268
+ direction: "asc"
269
+ } : current.direction === "asc" ? {
270
+ column,
271
+ direction: "desc"
272
+ } : null);
193
273
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(TableContainer, {
194
274
  sx: {
195
275
  maxHeight,
@@ -201,12 +281,19 @@ function ResultTable({ rows, maxHeight = 320 }) {
201
281
  size: "small",
202
282
  stickyHeader: true,
203
283
  children: [/* @__PURE__ */ jsx(TableHead, { children: /* @__PURE__ */ jsx(TableRow, { children: columns.map((column) => /* @__PURE__ */ jsx(TableCell, {
284
+ sortDirection: sort?.column === column ? sort.direction : false,
204
285
  sx: {
205
286
  fontFamily: MONO,
206
- fontWeight: 700
287
+ fontWeight: 700,
288
+ whiteSpace: "nowrap"
207
289
  },
208
- children: column
209
- }, column)) }) }), /* @__PURE__ */ jsx(TableBody, { children: rows.slice(0, MAX_ROWS).map((row, index) => /* @__PURE__ */ jsx(TableRow, {
290
+ children: /* @__PURE__ */ jsx(TableSortLabel, {
291
+ active: sort?.column === column,
292
+ direction: sort?.column === column ? sort.direction : "asc",
293
+ onClick: () => toggleSort(column),
294
+ children: column
295
+ })
296
+ }, column)) }) }), /* @__PURE__ */ jsx(TableBody, { children: sorted.slice(0, MAX_ROWS).map((row, index) => /* @__PURE__ */ jsx(TableRow, {
210
297
  hover: true,
211
298
  children: columns.map((column) => /* @__PURE__ */ jsx(TableCell, {
212
299
  align: typeof row[column] === "number" ? "right" : "left",
@@ -508,6 +595,483 @@ function FiltersPanel({ devtools }) {
508
595
  });
509
596
  }
510
597
  //#endregion
598
+ //#region node_modules/react-dom/cjs/react-dom.production.js
599
+ /**
600
+ * @license React
601
+ * react-dom.production.js
602
+ *
603
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
604
+ *
605
+ * This source code is licensed under the MIT license found in the
606
+ * LICENSE file in the root directory of this source tree.
607
+ */
608
+ var require_react_dom_production = /* @__PURE__ */ __commonJSMin(((exports) => {
609
+ var React = __require("react");
610
+ function formatProdErrorMessage(code) {
611
+ var url = "https://react.dev/errors/" + code;
612
+ if (1 < arguments.length) {
613
+ url += "?args[]=" + encodeURIComponent(arguments[1]);
614
+ for (var i = 2; i < arguments.length; i++) url += "&args[]=" + encodeURIComponent(arguments[i]);
615
+ }
616
+ return "Minified React error #" + code + "; visit " + url + " for the full message or use the non-minified dev environment for full errors and additional helpful warnings.";
617
+ }
618
+ function noop() {}
619
+ var Internals = {
620
+ d: {
621
+ f: noop,
622
+ r: function() {
623
+ throw Error(formatProdErrorMessage(522));
624
+ },
625
+ D: noop,
626
+ C: noop,
627
+ L: noop,
628
+ m: noop,
629
+ X: noop,
630
+ S: noop,
631
+ M: noop
632
+ },
633
+ p: 0,
634
+ findDOMNode: null
635
+ };
636
+ var REACT_PORTAL_TYPE = Symbol.for("react.portal");
637
+ var REACT_RECOVERABLE_TYPE = Symbol.for("react.recoverable");
638
+ var REACT_OPTIMISTIC_KEY = Symbol.for("react.optimistic_key");
639
+ function createPortal$1(children, containerInfo, implementation) {
640
+ var key = 3 < arguments.length && void 0 !== arguments[3] ? arguments[3] : null;
641
+ return {
642
+ $$typeof: REACT_PORTAL_TYPE,
643
+ key: null == key ? null : key === REACT_OPTIMISTIC_KEY ? REACT_OPTIMISTIC_KEY : "" + key,
644
+ children,
645
+ containerInfo,
646
+ implementation
647
+ };
648
+ }
649
+ var ReactSharedInternals = React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
650
+ function getCrossOriginStringAs(as, input) {
651
+ if ("font" === as) return "";
652
+ if ("string" === typeof input) return "use-credentials" === input ? input : "";
653
+ }
654
+ exports.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE = Internals;
655
+ exports.browser = function(reason) {
656
+ return {
657
+ $$typeof: REACT_RECOVERABLE_TYPE,
658
+ _reason: reason
659
+ };
660
+ };
661
+ exports.createPortal = function(children, container) {
662
+ var key = 2 < arguments.length && void 0 !== arguments[2] ? arguments[2] : null;
663
+ if (!container || 1 !== container.nodeType && 9 !== container.nodeType && 11 !== container.nodeType) throw Error(formatProdErrorMessage(299));
664
+ return createPortal$1(children, container, null, key);
665
+ };
666
+ exports.flushSync = function(fn) {
667
+ var previousTransition = ReactSharedInternals.T, previousUpdatePriority = Internals.p;
668
+ try {
669
+ if (ReactSharedInternals.T = null, Internals.p = 2, fn) return fn();
670
+ } finally {
671
+ ReactSharedInternals.T = previousTransition, Internals.p = previousUpdatePriority, Internals.d.f();
672
+ }
673
+ };
674
+ exports.preconnect = function(href, options) {
675
+ "string" === typeof href && (options ? (options = options.crossOrigin, options = "string" === typeof options ? "use-credentials" === options ? options : "" : void 0) : options = null, Internals.d.C(href, options));
676
+ };
677
+ exports.prefetchDNS = function(href) {
678
+ "string" === typeof href && Internals.d.D(href);
679
+ };
680
+ exports.preinit = function(href, options) {
681
+ if ("string" === typeof href && options && "string" === typeof options.as) {
682
+ var as = options.as, crossOrigin = getCrossOriginStringAs(as, options.crossOrigin), integrity = "string" === typeof options.integrity ? options.integrity : void 0, fetchPriority = "string" === typeof options.fetchPriority ? options.fetchPriority : void 0;
683
+ "style" === as ? Internals.d.S(href, "string" === typeof options.precedence ? options.precedence : void 0, {
684
+ crossOrigin,
685
+ integrity,
686
+ fetchPriority
687
+ }) : "script" === as && Internals.d.X(href, {
688
+ crossOrigin,
689
+ integrity,
690
+ fetchPriority,
691
+ nonce: "string" === typeof options.nonce ? options.nonce : void 0
692
+ });
693
+ }
694
+ };
695
+ exports.preinitModule = function(href, options) {
696
+ if ("string" === typeof href) if ("object" === typeof options && null !== options) {
697
+ if (null == options.as || "script" === options.as) {
698
+ var crossOrigin = getCrossOriginStringAs(options.as, options.crossOrigin);
699
+ Internals.d.M(href, {
700
+ crossOrigin,
701
+ integrity: "string" === typeof options.integrity ? options.integrity : void 0,
702
+ nonce: "string" === typeof options.nonce ? options.nonce : void 0,
703
+ fetchPriority: "string" === typeof options.fetchPriority ? options.fetchPriority : void 0
704
+ });
705
+ }
706
+ } else options ?? Internals.d.M(href);
707
+ };
708
+ exports.preload = function(href, options) {
709
+ if ("string" === typeof href && "object" === typeof options && null !== options && "string" === typeof options.as) {
710
+ var as = options.as, crossOrigin = getCrossOriginStringAs(as, options.crossOrigin);
711
+ Internals.d.L(href, as, {
712
+ crossOrigin,
713
+ integrity: "string" === typeof options.integrity ? options.integrity : void 0,
714
+ nonce: "string" === typeof options.nonce ? options.nonce : void 0,
715
+ type: "string" === typeof options.type ? options.type : void 0,
716
+ fetchPriority: "string" === typeof options.fetchPriority ? options.fetchPriority : void 0,
717
+ referrerPolicy: "string" === typeof options.referrerPolicy ? options.referrerPolicy : void 0,
718
+ imageSrcSet: "string" === typeof options.imageSrcSet ? options.imageSrcSet : void 0,
719
+ imageSizes: "string" === typeof options.imageSizes ? options.imageSizes : void 0,
720
+ media: "string" === typeof options.media ? options.media : void 0
721
+ });
722
+ }
723
+ };
724
+ exports.preloadModule = function(href, options) {
725
+ if ("string" === typeof href) if (options) {
726
+ var crossOrigin = getCrossOriginStringAs(options.as, options.crossOrigin);
727
+ Internals.d.m(href, {
728
+ as: "string" === typeof options.as && "script" !== options.as ? options.as : void 0,
729
+ crossOrigin,
730
+ integrity: "string" === typeof options.integrity ? options.integrity : void 0,
731
+ nonce: "string" === typeof options.nonce ? options.nonce : void 0,
732
+ fetchPriority: "string" === typeof options.fetchPriority ? options.fetchPriority : void 0
733
+ });
734
+ } else Internals.d.m(href);
735
+ };
736
+ exports.requestFormReset = function(form) {
737
+ Internals.d.r(form);
738
+ };
739
+ exports.unstable_batchedUpdates = function(fn, a) {
740
+ return fn(a);
741
+ };
742
+ exports.useFormState = function(action, initialState, permalink) {
743
+ return ReactSharedInternals.H.useFormState(action, initialState, permalink);
744
+ };
745
+ exports.useFormStatus = function() {
746
+ return ReactSharedInternals.H.useHostTransitionStatus();
747
+ };
748
+ exports.version = "19.3.0";
749
+ }));
750
+ //#endregion
751
+ //#region node_modules/react-dom/cjs/react-dom.development.js
752
+ /**
753
+ * @license React
754
+ * react-dom.development.js
755
+ *
756
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
757
+ *
758
+ * This source code is licensed under the MIT license found in the
759
+ * LICENSE file in the root directory of this source tree.
760
+ */
761
+ var require_react_dom_development = /* @__PURE__ */ __commonJSMin(((exports) => {
762
+ "production" !== process.env.NODE_ENV && (function() {
763
+ function noop() {}
764
+ function testStringCoercion(value) {
765
+ return "" + value;
766
+ }
767
+ function createPortal$1(children, containerInfo, implementation) {
768
+ var key = 3 < arguments.length && void 0 !== arguments[3] ? arguments[3] : null;
769
+ if (null == key) key = null;
770
+ else if (key === REACT_OPTIMISTIC_KEY) key = REACT_OPTIMISTIC_KEY;
771
+ else {
772
+ try {
773
+ testStringCoercion(key);
774
+ var JSCompiler_inline_result = !1;
775
+ } catch (e) {
776
+ JSCompiler_inline_result = !0;
777
+ }
778
+ JSCompiler_inline_result && (console.error("The provided key is an unsupported type %s. This value must be coerced to a string before using it here.", "function" === typeof Symbol && Symbol.toStringTag && key[Symbol.toStringTag] || key.constructor.name || "Object"), testStringCoercion(key));
779
+ key = "" + key;
780
+ }
781
+ return {
782
+ $$typeof: REACT_PORTAL_TYPE,
783
+ key,
784
+ children,
785
+ containerInfo,
786
+ implementation
787
+ };
788
+ }
789
+ function getCrossOriginStringAs(as, input) {
790
+ if ("font" === as) return "";
791
+ if ("string" === typeof input) return "use-credentials" === input ? input : "";
792
+ }
793
+ function getValueDescriptorExpectingObjectForWarning(thing) {
794
+ return null === thing ? "`null`" : void 0 === thing ? "`undefined`" : "" === thing ? "an empty string" : "something with type \"" + typeof thing + "\"";
795
+ }
796
+ function getValueDescriptorExpectingEnumForWarning(thing) {
797
+ return null === thing ? "`null`" : void 0 === thing ? "`undefined`" : "" === thing ? "an empty string" : "string" === typeof thing ? JSON.stringify(thing) : "number" === typeof thing ? "`" + thing + "`" : "something with type \"" + typeof thing + "\"";
798
+ }
799
+ function resolveDispatcher() {
800
+ var dispatcher = ReactSharedInternals.H;
801
+ null === dispatcher && console.error("Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:\n1. You might have mismatching versions of React and the renderer (such as React DOM)\n2. You might be breaking the Rules of Hooks\n3. You might have more than one copy of React in the same app\nSee https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem.");
802
+ return dispatcher;
803
+ }
804
+ "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());
805
+ var React = __require("react"), Internals = {
806
+ d: {
807
+ f: noop,
808
+ r: function() {
809
+ throw Error("Invalid form element. requestFormReset must be passed a form that was rendered by React.");
810
+ },
811
+ D: noop,
812
+ C: noop,
813
+ L: noop,
814
+ m: noop,
815
+ X: noop,
816
+ S: noop,
817
+ M: noop
818
+ },
819
+ p: 0,
820
+ findDOMNode: null
821
+ }, REACT_PORTAL_TYPE = Symbol.for("react.portal"), REACT_RECOVERABLE_TYPE = Symbol.for("react.recoverable"), REACT_OPTIMISTIC_KEY = Symbol.for("react.optimistic_key"), ReactSharedInternals = React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
822
+ "function" === typeof Map && null != Map.prototype && "function" === typeof Map.prototype.forEach && "function" === typeof Set && null != Set.prototype && "function" === typeof Set.prototype.clear && "function" === typeof Set.prototype.forEach || console.error("React depends on Map and Set built-in types. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills");
823
+ exports.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE = Internals;
824
+ exports.browser = function(reason) {
825
+ return {
826
+ $$typeof: REACT_RECOVERABLE_TYPE,
827
+ _reason: reason
828
+ };
829
+ };
830
+ exports.createPortal = function(children, container) {
831
+ var key = 2 < arguments.length && void 0 !== arguments[2] ? arguments[2] : null;
832
+ if (!container || 1 !== container.nodeType && 9 !== container.nodeType && 11 !== container.nodeType) throw Error("Target container is not a DOM element.");
833
+ return createPortal$1(children, container, null, key);
834
+ };
835
+ exports.flushSync = function(fn) {
836
+ var previousTransition = ReactSharedInternals.T, previousUpdatePriority = Internals.p;
837
+ try {
838
+ if (ReactSharedInternals.T = null, Internals.p = 2, fn) return fn();
839
+ } finally {
840
+ ReactSharedInternals.T = previousTransition, Internals.p = previousUpdatePriority, Internals.d.f() && console.error("flushSync was called from inside a lifecycle method. React cannot flush when React is already rendering. Consider moving this call to a scheduler task or micro task.");
841
+ }
842
+ };
843
+ exports.preconnect = function(href, options) {
844
+ "string" === typeof href && href ? null != options && "object" !== typeof options ? console.error("ReactDOM.preconnect(): Expected the `options` argument (second) to be an object but encountered %s instead. The only supported option at this time is `crossOrigin` which accepts a string.", getValueDescriptorExpectingEnumForWarning(options)) : null != options && "string" !== typeof options.crossOrigin && console.error("ReactDOM.preconnect(): Expected the `crossOrigin` option (second argument) to be a string but encountered %s instead. Try removing this option or passing a string value instead.", getValueDescriptorExpectingObjectForWarning(options.crossOrigin)) : console.error("ReactDOM.preconnect(): Expected the `href` argument (first) to be a non-empty string but encountered %s instead.", getValueDescriptorExpectingObjectForWarning(href));
845
+ "string" === typeof href && (options ? (options = options.crossOrigin, options = "string" === typeof options ? "use-credentials" === options ? options : "" : void 0) : options = null, Internals.d.C(href, options));
846
+ };
847
+ exports.prefetchDNS = function(href) {
848
+ if ("string" !== typeof href || !href) console.error("ReactDOM.prefetchDNS(): Expected the `href` argument (first) to be a non-empty string but encountered %s instead.", getValueDescriptorExpectingObjectForWarning(href));
849
+ else if (1 < arguments.length) {
850
+ var options = arguments[1];
851
+ "object" === typeof options && options.hasOwnProperty("crossOrigin") ? console.error("ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered %s as a second argument instead. This argument is reserved for future options and is currently disallowed. It looks like the you are attempting to set a crossOrigin property for this DNS lookup hint. Browsers do not perform DNS queries using CORS and setting this attribute on the resource hint has no effect. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.", getValueDescriptorExpectingEnumForWarning(options)) : console.error("ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered %s as a second argument instead. This argument is reserved for future options and is currently disallowed. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.", getValueDescriptorExpectingEnumForWarning(options));
852
+ }
853
+ "string" === typeof href && Internals.d.D(href);
854
+ };
855
+ exports.preinit = function(href, options) {
856
+ "string" === typeof href && href ? null == options || "object" !== typeof options ? console.error("ReactDOM.preinit(): Expected the `options` argument (second) to be an object with an `as` property describing the type of resource to be preinitialized but encountered %s instead.", getValueDescriptorExpectingEnumForWarning(options)) : "style" !== options.as && "script" !== options.as && console.error("ReactDOM.preinit(): Expected the `as` property in the `options` argument (second) to contain a valid value describing the type of resource to be preinitialized but encountered %s instead. Valid values for `as` are \"style\" and \"script\".", getValueDescriptorExpectingEnumForWarning(options.as)) : console.error("ReactDOM.preinit(): Expected the `href` argument (first) to be a non-empty string but encountered %s instead.", getValueDescriptorExpectingObjectForWarning(href));
857
+ if ("string" === typeof href && options && "string" === typeof options.as) {
858
+ var as = options.as, crossOrigin = getCrossOriginStringAs(as, options.crossOrigin), integrity = "string" === typeof options.integrity ? options.integrity : void 0, fetchPriority = "string" === typeof options.fetchPriority ? options.fetchPriority : void 0;
859
+ "style" === as ? Internals.d.S(href, "string" === typeof options.precedence ? options.precedence : void 0, {
860
+ crossOrigin,
861
+ integrity,
862
+ fetchPriority
863
+ }) : "script" === as && Internals.d.X(href, {
864
+ crossOrigin,
865
+ integrity,
866
+ fetchPriority,
867
+ nonce: "string" === typeof options.nonce ? options.nonce : void 0
868
+ });
869
+ }
870
+ };
871
+ exports.preinitModule = function(href, options) {
872
+ var encountered = "";
873
+ "string" === typeof href && href || (encountered += " The `href` argument encountered was " + getValueDescriptorExpectingObjectForWarning(href) + ".");
874
+ void 0 !== options && "object" !== typeof options ? encountered += " The `options` argument encountered was " + getValueDescriptorExpectingObjectForWarning(options) + "." : options && "as" in options && "script" !== options.as && (encountered += " The `as` option encountered was " + getValueDescriptorExpectingEnumForWarning(options.as) + ".");
875
+ if (encountered) console.error("ReactDOM.preinitModule(): Expected up to two arguments, a non-empty `href` string and, optionally, an `options` object with a valid `as` property.%s", encountered);
876
+ else switch (encountered = options && "string" === typeof options.as ? options.as : "script", encountered) {
877
+ case "script": break;
878
+ default: encountered = getValueDescriptorExpectingEnumForWarning(encountered), console.error("ReactDOM.preinitModule(): Currently the only supported \"as\" type for this function is \"script\" but received \"%s\" instead. This warning was generated for `href` \"%s\". In the future other module types will be supported, aligning with the import-attributes proposal. Learn more here: (https://github.com/tc39/proposal-import-attributes)", encountered, href);
879
+ }
880
+ if ("string" === typeof href) if ("object" === typeof options && null !== options) {
881
+ if (null == options.as || "script" === options.as) encountered = getCrossOriginStringAs(options.as, options.crossOrigin), Internals.d.M(href, {
882
+ crossOrigin: encountered,
883
+ integrity: "string" === typeof options.integrity ? options.integrity : void 0,
884
+ nonce: "string" === typeof options.nonce ? options.nonce : void 0,
885
+ fetchPriority: "string" === typeof options.fetchPriority ? options.fetchPriority : void 0
886
+ });
887
+ } else options ?? Internals.d.M(href);
888
+ };
889
+ exports.preload = function(href, options) {
890
+ var encountered = "";
891
+ "string" === typeof href && href || (encountered += " The `href` argument encountered was " + getValueDescriptorExpectingObjectForWarning(href) + ".");
892
+ null == options || "object" !== typeof options ? encountered += " The `options` argument encountered was " + getValueDescriptorExpectingObjectForWarning(options) + "." : "string" === typeof options.as && options.as || (encountered += " The `as` option encountered was " + getValueDescriptorExpectingObjectForWarning(options.as) + ".");
893
+ encountered && console.error("ReactDOM.preload(): Expected two arguments, a non-empty `href` string and an `options` object with an `as` property valid for a `<link rel=\"preload\" as=\"...\" />` tag.%s", encountered);
894
+ if ("string" === typeof href && "object" === typeof options && null !== options && "string" === typeof options.as) {
895
+ encountered = options.as;
896
+ var crossOrigin = getCrossOriginStringAs(encountered, options.crossOrigin);
897
+ Internals.d.L(href, encountered, {
898
+ crossOrigin,
899
+ integrity: "string" === typeof options.integrity ? options.integrity : void 0,
900
+ nonce: "string" === typeof options.nonce ? options.nonce : void 0,
901
+ type: "string" === typeof options.type ? options.type : void 0,
902
+ fetchPriority: "string" === typeof options.fetchPriority ? options.fetchPriority : void 0,
903
+ referrerPolicy: "string" === typeof options.referrerPolicy ? options.referrerPolicy : void 0,
904
+ imageSrcSet: "string" === typeof options.imageSrcSet ? options.imageSrcSet : void 0,
905
+ imageSizes: "string" === typeof options.imageSizes ? options.imageSizes : void 0,
906
+ media: "string" === typeof options.media ? options.media : void 0
907
+ });
908
+ }
909
+ };
910
+ exports.preloadModule = function(href, options) {
911
+ var encountered = "";
912
+ "string" === typeof href && href || (encountered += " The `href` argument encountered was " + getValueDescriptorExpectingObjectForWarning(href) + ".");
913
+ void 0 !== options && "object" !== typeof options ? encountered += " The `options` argument encountered was " + getValueDescriptorExpectingObjectForWarning(options) + "." : options && "as" in options && "string" !== typeof options.as && (encountered += " The `as` option encountered was " + getValueDescriptorExpectingObjectForWarning(options.as) + ".");
914
+ encountered && console.error("ReactDOM.preloadModule(): Expected two arguments, a non-empty `href` string and, optionally, an `options` object with an `as` property valid for a `<link rel=\"modulepreload\" as=\"...\" />` tag.%s", encountered);
915
+ "string" === typeof href && (options ? (encountered = getCrossOriginStringAs(options.as, options.crossOrigin), Internals.d.m(href, {
916
+ as: "string" === typeof options.as && "script" !== options.as ? options.as : void 0,
917
+ crossOrigin: encountered,
918
+ integrity: "string" === typeof options.integrity ? options.integrity : void 0,
919
+ nonce: "string" === typeof options.nonce ? options.nonce : void 0,
920
+ fetchPriority: "string" === typeof options.fetchPriority ? options.fetchPriority : void 0
921
+ })) : Internals.d.m(href));
922
+ };
923
+ exports.requestFormReset = function(form) {
924
+ Internals.d.r(form);
925
+ };
926
+ exports.unstable_batchedUpdates = function(fn, a) {
927
+ return fn(a);
928
+ };
929
+ exports.useFormState = function(action, initialState, permalink) {
930
+ return resolveDispatcher().useFormState(action, initialState, permalink);
931
+ };
932
+ exports.useFormStatus = function() {
933
+ return resolveDispatcher().useHostTransitionStatus();
934
+ };
935
+ exports.version = "19.3.0";
936
+ "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error());
937
+ })();
938
+ }));
939
+ //#endregion
940
+ //#region src/devtools/DevtoolsWindow.tsx
941
+ var import_react_dom = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
942
+ function checkDCE() {
943
+ if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === "undefined" || typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE !== "function") return;
944
+ if (process.env.NODE_ENV !== "production") throw new Error("^_^");
945
+ try {
946
+ __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(checkDCE);
947
+ } catch (err) {
948
+ console.error(err);
949
+ }
950
+ }
951
+ if (process.env.NODE_ENV === "production") {
952
+ checkDCE();
953
+ module.exports = require_react_dom_production();
954
+ } else module.exports = require_react_dom_development();
955
+ })))();
956
+ /**
957
+ * Devtools numa janela separada do navegador (popup), para usar com a página em tela cheia ou em
958
+ * outro monitor. O painel continua sendo renderizado pela árvore React da página (mesmo contexto,
959
+ * mesmo store), só que via portal no `document` do popup:
960
+ *
961
+ * - os estilos do emotion/MUI vão para o `<head>` da página; aqui eles são espelhados no popup;
962
+ * - menus, tooltips e diálogos do MUI montam no `body` da página por padrão: o tema da janela
963
+ * aponta o `container` deles para o popup.
964
+ *
965
+ * Recarregar a página derruba o vínculo (o popup é de outra árvore): o popup avisa e o botão do
966
+ * devtools na página reconecta (abrir popup exige um clique, o navegador bloqueia sem gesto).
967
+ */
968
+ const WINDOW_NAME = "bm-bi-devtools";
969
+ /** Abre (ou reaproveita, se ainda estiver aberta) a janela do devtools. `null` = bloqueada. */
970
+ function openDevtoolsWindow() {
971
+ const width = Math.min(1100, window.screen.availWidth);
972
+ const height = Math.min(720, window.screen.availHeight);
973
+ return window.open("", WINDOW_NAME, `popup,width=${width},height=${height}`);
974
+ }
975
+ const isStyleNode = (node) => node instanceof HTMLStyleElement || node instanceof HTMLLinkElement && node.rel === "stylesheet";
976
+ /** Cópia de um `<style>`/`<link>`; estilo inserido por `insertRule` (sem texto) vai pelas regras. */
977
+ function cloneStyle(node, target) {
978
+ const clone = target.importNode(node, true);
979
+ if (node instanceof HTMLStyleElement && !node.textContent && node.sheet?.cssRules.length) clone.textContent = [...node.sheet.cssRules].map((rule) => rule.cssText).join("\n");
980
+ return clone;
981
+ }
982
+ /** Copia os estilos da página para o popup e acompanha os que entrarem/saírem depois. */
983
+ function mirrorStyles(source, target) {
984
+ const clones = /* @__PURE__ */ new Map();
985
+ const add = (node) => {
986
+ if (!isStyleNode(node) || clones.has(node)) return;
987
+ const clone = cloneStyle(node, target);
988
+ clones.set(node, clone);
989
+ target.head.appendChild(clone);
990
+ };
991
+ for (const node of source.head.querySelectorAll("style, link[rel=\"stylesheet\"]")) add(node);
992
+ const observer = new MutationObserver((mutations) => {
993
+ for (const mutation of mutations) {
994
+ if (mutation.type === "characterData" || mutation.target !== source.head) {
995
+ const style = (mutation.target instanceof Element ? mutation.target : mutation.target.parentElement)?.closest("style");
996
+ const clone = style && clones.get(style);
997
+ if (style && clone) {
998
+ const next = cloneStyle(style, target);
999
+ target.head.replaceChild(next, clone);
1000
+ clones.set(style, next);
1001
+ }
1002
+ continue;
1003
+ }
1004
+ for (const node of mutation.addedNodes) add(node);
1005
+ for (const node of mutation.removedNodes) {
1006
+ clones.get(node)?.parentNode?.removeChild(clones.get(node));
1007
+ clones.delete(node);
1008
+ }
1009
+ }
1010
+ });
1011
+ observer.observe(source.head, {
1012
+ childList: true,
1013
+ subtree: true,
1014
+ characterData: true
1015
+ });
1016
+ return () => observer.disconnect();
1017
+ }
1018
+ /** Tema com os portais do MUI (menus, tooltips, diálogos) dentro do popup. */
1019
+ function windowTheme(base, container) {
1020
+ return createTheme(base, { components: {
1021
+ MuiModal: { defaultProps: { container } },
1022
+ MuiPopover: { defaultProps: { container } },
1023
+ MuiPopper: { defaultProps: { container } },
1024
+ MuiDialog: { defaultProps: { container } },
1025
+ MuiMenu: { defaultProps: { container } }
1026
+ } });
1027
+ }
1028
+ function DevtoolsWindow({ popup, title, theme, onClosed, children }) {
1029
+ const [root, setRoot] = useState(null);
1030
+ const onClosedRef = useRef(onClosed);
1031
+ onClosedRef.current = onClosed;
1032
+ useEffect(() => {
1033
+ const doc = popup.document;
1034
+ doc.title = title;
1035
+ doc.head.replaceChildren();
1036
+ doc.body.replaceChildren();
1037
+ const meta = doc.createElement("meta");
1038
+ meta.setAttribute("charset", "utf-8");
1039
+ doc.head.appendChild(meta);
1040
+ const base = doc.createElement("style");
1041
+ base.textContent = `html, body { height: 100%; margin: 0; background: ${theme.palette.background.paper}; color-scheme: dark; } body { font-family: ${theme.typography.fontFamily}; } #${WINDOW_NAME} { height: 100%; display: flex; flex-direction: column; }`;
1042
+ doc.head.appendChild(base);
1043
+ const container = doc.createElement("div");
1044
+ container.id = WINDOW_NAME;
1045
+ doc.body.appendChild(container);
1046
+ const stopMirror = mirrorStyles(document, doc);
1047
+ setRoot(container);
1048
+ const poll = setInterval(() => {
1049
+ if (popup.closed) onClosedRef.current();
1050
+ }, 400);
1051
+ const onPageHide = () => {
1052
+ stopMirror();
1053
+ doc.body.innerHTML = `<p style="font: 14px sans-serif; color: #ccc; padding: 24px">A página do dashboard recarregou. Clique no botão <b style="color: #F58220">◆ BI</b> da página para reconectar o devtools a esta janela.</p>`;
1054
+ };
1055
+ window.addEventListener("pagehide", onPageHide);
1056
+ return () => {
1057
+ clearInterval(poll);
1058
+ window.removeEventListener("pagehide", onPageHide);
1059
+ stopMirror();
1060
+ setRoot(null);
1061
+ };
1062
+ }, [
1063
+ popup,
1064
+ title,
1065
+ theme
1066
+ ]);
1067
+ const localTheme = useMemo(() => root && windowTheme(theme, root), [theme, root]);
1068
+ if (!root || !localTheme) return null;
1069
+ return (0, import_react_dom.createPortal)(/* @__PURE__ */ jsx(ThemeProvider, {
1070
+ theme: localTheme,
1071
+ children
1072
+ }), root);
1073
+ }
1074
+ //#endregion
511
1075
  //#region src/devtools/Inspector.tsx
512
1076
  /**
513
1077
  * Liga componente da página <-> consulta do devtools. Os componentes marcam o elemento raiz com
@@ -676,7 +1240,7 @@ function useWidth() {
676
1240
  const [width, setWidth] = useState(Number.POSITIVE_INFINITY);
677
1241
  useEffect(() => {
678
1242
  if (!element) return;
679
- const observer = new ResizeObserver(([entry]) => entry && setWidth(entry.contentRect.width));
1243
+ const observer = new ((element.ownerDocument.defaultView?.ResizeObserver) ?? ResizeObserver)(([entry]) => entry && setWidth(entry.contentRect.width));
680
1244
  observer.observe(element);
681
1245
  return () => observer.disconnect();
682
1246
  }, [element]);
@@ -696,6 +1260,18 @@ const uniqueId = (charts, base) => {
696
1260
  while (ids.has(`${base}-${n}`)) n++;
697
1261
  return `${base}-${n}`;
698
1262
  };
1263
+ /** Objeto sem nenhum campo sai do JSON. */
1264
+ const nonEmpty = (object) => Object.keys(object).length ? object : void 0;
1265
+ /** Campos do card de um gráfico, com o padrão do layout (`cards`) no lugar do padrão da lib. */
1266
+ const chartCardFields = (cards) => Object.fromEntries(Object.entries(CARD_FIELDS).map(([key, spec]) => [key, {
1267
+ ...spec,
1268
+ default: cards?.[key] ?? spec.default
1269
+ }]));
1270
+ const cardsSummary = (cards) => {
1271
+ const { gap, ...style } = cards ?? {};
1272
+ const custom = Object.keys(style).length;
1273
+ return `espaço ${String(gap ?? CARDS_FIELDS.gap?.default)}px · ${custom ? `${custom} ajuste(s) de estilo` : "estilo padrão"}`;
1274
+ };
699
1275
  /** Campo opcional vazio sai do objeto, para o JSON ficar só com o que foi definido. */
700
1276
  function withField(object, key, value) {
701
1277
  const next = { ...object };
@@ -809,7 +1385,7 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
809
1385
  const charts = editing.charts;
810
1386
  const filters = editing.filters ?? [];
811
1387
  const selectedFilter = selection?.kind === "filter" ? filters.find((filter) => filter.dimension === selection.key) ?? null : null;
812
- const selected = selection?.kind === "filter" || selection?.kind === "canvas" ? null : charts.find((chart) => chart.id === selection?.key) ?? charts[0] ?? null;
1388
+ const selected = selection?.kind === "filter" || selection?.kind === "canvas" || selection?.kind === "cards" ? null : charts.find((chart) => chart.id === selection?.key) ?? charts[0] ?? null;
813
1389
  const layoutKey = entry.registered.id;
814
1390
  const differsFromFile = canonicalJson(editing) !== canonicalJson(entry.registered);
815
1391
  const update = (next) => {
@@ -826,6 +1402,7 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
826
1402
  });
827
1403
  const patchChart = (id, change) => setCharts(charts.map((chart) => chart.id === id ? change(chart) : chart));
828
1404
  const setCanvas = (next) => update(withField(editing, "canvas", next));
1405
+ const setCards = (key, value) => update(withField(editing, "cards", nonEmpty(withField(editing.cards ?? {}, key, value))));
829
1406
  const setFilters = (next) => update({
830
1407
  ...editing,
831
1408
  filters: next
@@ -884,7 +1461,7 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
884
1461
  };
885
1462
  const stacked = rootWidth < STACK_WIDTH;
886
1463
  const formColumns = editorWidth < 420 ? 1 : editorWidth < 720 ? 2 : 4;
887
- const selectedTitle = selection?.kind === "canvas" ? "Tela" : selectedFilter ? selectedFilter.label || model.dimensions[selectedFilter.dimension]?.label || selectedFilter.dimension : selected ? selected.title || selected.id : "";
1464
+ const selectedTitle = selection?.kind === "canvas" ? "Tela" : selection?.kind === "cards" ? "Cards" : selectedFilter ? selectedFilter.label || model.dimensions[selectedFilter.dimension]?.label || selectedFilter.dimension : selected ? selected.title || selected.id : "";
888
1465
  const hover = (item) => onHover(item && itemQueryId(layoutKey, item));
889
1466
  const add = (type, base) => {
890
1467
  setAddMenu(null);
@@ -992,9 +1569,10 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
992
1569
  children: [item.registered.id, item.draft ? " · rascunho" : ""]
993
1570
  }, item.registered.id))
994
1571
  }),
995
- /* @__PURE__ */ jsx(Section, {
1572
+ /* @__PURE__ */ jsxs(Section, {
996
1573
  title: "Tela",
997
- children: /* @__PURE__ */ jsx(ItemRow, {
1574
+ collapseKey: "layout:lista:tela",
1575
+ children: [/* @__PURE__ */ jsx(ItemRow, {
998
1576
  title: editing.canvas ? "Tamanho fixo" : "Responsiva",
999
1577
  subtitle: editing.canvas ? presetLabel(editing.canvas) : "reorganiza conforme a largura",
1000
1578
  selected: selection?.kind === "canvas",
@@ -1002,10 +1580,19 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
1002
1580
  setSelection({ kind: "canvas" });
1003
1581
  if (stacked) setSidebar(false);
1004
1582
  }
1005
- })
1583
+ }), /* @__PURE__ */ jsx(ItemRow, {
1584
+ title: "Cards",
1585
+ subtitle: cardsSummary(editing.cards),
1586
+ selected: selection?.kind === "cards",
1587
+ onSelect: () => {
1588
+ setSelection({ kind: "cards" });
1589
+ if (stacked) setSidebar(false);
1590
+ }
1591
+ })]
1006
1592
  }),
1007
1593
  /* @__PURE__ */ jsxs(Section, {
1008
1594
  title: `Filtros (${filters.length})`,
1595
+ collapseKey: "layout:lista:filtros",
1009
1596
  actions: /* @__PURE__ */ jsx(Button, {
1010
1597
  size: "small",
1011
1598
  onClick: addFilter,
@@ -1037,6 +1624,7 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
1037
1624
  }),
1038
1625
  /* @__PURE__ */ jsx(Section, {
1039
1626
  title: `Gráficos (${charts.length})`,
1627
+ collapseKey: "layout:lista:graficos",
1040
1628
  actions: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
1041
1629
  size: "small",
1042
1630
  onClick: (e) => setAddMenu(e.currentTarget),
@@ -1113,7 +1701,23 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
1113
1701
  canvas: editing.canvas,
1114
1702
  columns: formColumns,
1115
1703
  onChange: setCanvas
1116
- }) : selectedFilter ? /* @__PURE__ */ jsx(FilterForm, {
1704
+ }) : selection?.kind === "cards" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Typography, {
1705
+ sx: {
1706
+ fontSize: 12,
1707
+ color: "text.secondary",
1708
+ mb: 1
1709
+ },
1710
+ children: "Estilo padrão dos cards de todos os gráficos; cada gráfico pode sobrepor na seção Card dele. Vazio = padrão do componente."
1711
+ }), /* @__PURE__ */ jsx(FieldsForm, {
1712
+ fields: CARDS_FIELDS,
1713
+ values: editing.cards ?? {},
1714
+ onSet: setCards,
1715
+ model,
1716
+ formatters: [],
1717
+ columns: [],
1718
+ formColumns,
1719
+ chosenDimensions: []
1720
+ })] }) : selectedFilter ? /* @__PURE__ */ jsx(FilterForm, {
1117
1721
  filter: selectedFilter,
1118
1722
  model,
1119
1723
  formatters: entry.parts.filters ?? [],
@@ -1127,6 +1731,7 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
1127
1731
  }
1128
1732
  }, `filter:${selectedFilter.dimension}`) : selected ? /* @__PURE__ */ jsx(ChartForm, {
1129
1733
  chart: selected,
1734
+ cards: editing.cards,
1130
1735
  types: chartTypes,
1131
1736
  model,
1132
1737
  formatters: entry.parts.charts ?? [],
@@ -1234,6 +1839,7 @@ function CanvasForm({ canvas, columns, onChange }) {
1234
1839
  const number = (value) => value === "" ? void 0 : Number(value);
1235
1840
  return /* @__PURE__ */ jsx(Section, {
1236
1841
  title: "Tela",
1842
+ collapseKey: "layout:form:Tela",
1237
1843
  children: /* @__PURE__ */ jsxs(Box, {
1238
1844
  sx: formGrid(columns),
1239
1845
  children: [/* @__PURE__ */ jsx(Box, {
@@ -1401,9 +2007,10 @@ function SmallButton({ title, disabled, onClick, children }) {
1401
2007
  }) })
1402
2008
  });
1403
2009
  }
1404
- function ChartForm({ chart, types, model, formatters, columns, onChange }) {
2010
+ function ChartForm({ chart, cards, types, model, formatters, columns, onChange }) {
1405
2011
  const set = (key, value) => onChange((current) => withField(current, key, value));
1406
- return /* @__PURE__ */ jsx(FieldsForm, {
2012
+ const setCard = (key, value) => onChange((current) => withField(current, "card", nonEmpty(withField(current.card ?? {}, key, value))));
2013
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(FieldsForm, {
1407
2014
  fields: types[chart.type]?.fields ?? {},
1408
2015
  values: chart,
1409
2016
  onSet: set,
@@ -1435,7 +2042,16 @@ function ChartForm({ chart, types, model, formatters, columns, onChange }) {
1435
2042
  onChange: (e) => set("id", e.target.value),
1436
2043
  sx: inputSx$1
1437
2044
  }), "Identificador no JSON (letras, números, \"-\" e \"_\")")] })
1438
- });
2045
+ }), /* @__PURE__ */ jsx(FieldsForm, {
2046
+ fields: chartCardFields(cards),
2047
+ values: chart.card ?? {},
2048
+ onSet: setCard,
2049
+ model,
2050
+ formatters: [],
2051
+ columns: [],
2052
+ formColumns: columns,
2053
+ chosenDimensions: []
2054
+ })] });
1439
2055
  }
1440
2056
  function FilterForm({ filter, model, formatters, columns, onChange }) {
1441
2057
  return /* @__PURE__ */ jsx(FieldsForm, {
@@ -1625,6 +2241,7 @@ function FieldsForm({ fields, values, onSet: set, model, formatters, columns, fo
1625
2241
  const inSection = entries.filter(([, spec]) => spec.section === section);
1626
2242
  return /* @__PURE__ */ jsx(Section, {
1627
2243
  title: section,
2244
+ collapseKey: `layout:form:${section}`,
1628
2245
  children: /* @__PURE__ */ jsxs(Box, {
1629
2246
  sx: formGrid(formColumns),
1630
2247
  children: [section === sections[0] && header, inSection.map(([key, spec]) => /* @__PURE__ */ jsx(Box, {
@@ -2705,6 +3322,7 @@ function track(event, onMove, onUp) {
2705
3322
  x: event.clientX,
2706
3323
  y: event.clientY
2707
3324
  };
3325
+ const win = event.currentTarget.ownerDocument.defaultView ?? window;
2708
3326
  let moved = false;
2709
3327
  const move = (moveEvent) => {
2710
3328
  const dx = moveEvent.clientX - start.x;
@@ -2713,12 +3331,12 @@ function track(event, onMove, onUp) {
2713
3331
  if (moved) onMove(dx, dy, moveEvent);
2714
3332
  };
2715
3333
  const up = (upEvent) => {
2716
- window.removeEventListener("mousemove", move);
2717
- window.removeEventListener("mouseup", up);
3334
+ win.removeEventListener("mousemove", move);
3335
+ win.removeEventListener("mouseup", up);
2718
3336
  onUp?.(upEvent, moved);
2719
3337
  };
2720
- window.addEventListener("mousemove", move);
2721
- window.addEventListener("mouseup", up);
3338
+ win.addEventListener("mousemove", move);
3339
+ win.addEventListener("mouseup", up);
2722
3340
  }
2723
3341
  /** Colunas citadas como `tabela.coluna` no SQL das dimensões/medidas. */
2724
3342
  function citedColumns(sql, table) {
@@ -3060,7 +3678,7 @@ function ModelDiagram({ editor, setEditor, columns, errors, warnings, loadingCol
3060
3678
  selectNode(node);
3061
3679
  return;
3062
3680
  }
3063
- const target = document.elementFromPoint(upEvent.clientX, upEvent.clientY)?.closest("[data-diagram-column]");
3681
+ const target = (canvas.current?.ownerDocument ?? document).elementFromPoint(upEvent.clientX, upEvent.clientY)?.closest("[data-diagram-column]");
3064
3682
  const targetNode = nodes.find((item) => item.id === target?.dataset.diagramNode);
3065
3683
  const targetColumn = target?.dataset.diagramColumn;
3066
3684
  if (targetNode && targetColumn) connect(node, column, targetNode, targetColumn);
@@ -4587,7 +5205,7 @@ function ModelPanel({ devtools, original, capabilities, tables, onTry, onSaved }
4587
5205
  }),
4588
5206
  /* @__PURE__ */ jsx(Button, {
4589
5207
  size: "small",
4590
- onClick: () => copyText(toModelCode(edited)),
5208
+ onClick: (e) => copyText(toModelCode(edited), e.currentTarget),
4591
5209
  children: "Copiar TS"
4592
5210
  }),
4593
5211
  /* @__PURE__ */ jsx(Button, {
@@ -4850,12 +5468,55 @@ function PlaygroundPanel({ model, selection, onSelectionChange }) {
4850
5468
  //#endregion
4851
5469
  //#region src/devtools/QueriesPanel.tsx
4852
5470
  const queryKey = (run) => JSON.stringify(run.query);
5471
+ const SORT_LABEL = {
5472
+ default: "ordem da página",
5473
+ label: "nome",
5474
+ duration: "mais lentas",
5475
+ rows: "mais linhas",
5476
+ status: "erros primeiro"
5477
+ };
5478
+ const SORT_KEY = "bm-dashboard-devtools:queries-sort";
5479
+ /** Problemas primeiro: erro, em andamento, cancelada, ok. */
5480
+ const STATUS_ORDER = {
5481
+ error: 0,
5482
+ loading: 1,
5483
+ cancelled: 2,
5484
+ success: 3
5485
+ };
5486
+ function loadSort() {
5487
+ try {
5488
+ const saved = localStorage.getItem(SORT_KEY);
5489
+ return saved && saved in SORT_LABEL ? saved : "default";
5490
+ } catch {
5491
+ return "default";
5492
+ }
5493
+ }
5494
+ /** Ordena a lista; empate mantém a ordem original (página ou mais recente primeiro). */
5495
+ function sortRuns(runs, key) {
5496
+ if (key === "default") return runs;
5497
+ const compare = (a, b) => {
5498
+ switch (key) {
5499
+ case "label": return a.label.localeCompare(b.label, "pt-BR", { numeric: true });
5500
+ case "duration": return (b.durationMs ?? Date.now() - b.startedAt) - (a.durationMs ?? Date.now() - a.startedAt);
5501
+ case "rows": return (b.rows?.length ?? -1) - (a.rows?.length ?? -1);
5502
+ case "status": return STATUS_ORDER[a.status] - STATUS_ORDER[b.status];
5503
+ }
5504
+ };
5505
+ return [...runs].sort(compare);
5506
+ }
4853
5507
  /** Consultas ativas na página (uma por componente) e o histórico de execuções. */
4854
5508
  function QueriesPanel({ devtools, capabilities, selected, onSelect, onHover, onClearServerCache }) {
4855
5509
  const { store, refresh } = devtools;
4856
5510
  const snapshot = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
4857
5511
  const [view, setView] = useState("active");
4858
- const runs = view === "active" ? snapshot.instances.map((instance) => instance.last) : snapshot.history;
5512
+ const [sort, setSort] = useState(loadSort);
5513
+ const changeSort = (next) => {
5514
+ setSort(next);
5515
+ try {
5516
+ localStorage.setItem(SORT_KEY, next);
5517
+ } catch {}
5518
+ };
5519
+ const runs = sortRuns(view === "active" ? snapshot.instances.map((instance) => instance.last) : snapshot.history, sort);
4859
5520
  const instanceById = new Map(snapshot.instances.map((item) => [item.instanceId, item]));
4860
5521
  const sameQuery = /* @__PURE__ */ new Map();
4861
5522
  for (const { last } of snapshot.instances) sameQuery.set(queryKey(last), (sameQuery.get(queryKey(last)) ?? 0) + 1);
@@ -4886,31 +5547,54 @@ function QueriesPanel({ devtools, capabilities, selected, onSelect, onHover, onC
4886
5547
  borderBottom: 1,
4887
5548
  borderColor: "divider"
4888
5549
  },
4889
- children: [/* @__PURE__ */ jsxs(ToggleButtonGroup, {
4890
- size: "small",
4891
- exclusive: true,
4892
- value: view,
4893
- onChange: (_, value) => value && setView(value),
4894
- children: [/* @__PURE__ */ jsxs(ToggleButton, {
4895
- value: "active",
4896
- children: [
4897
- "Ativas (",
4898
- snapshot.instances.length,
4899
- ")"
4900
- ]
4901
- }), /* @__PURE__ */ jsxs(ToggleButton, {
4902
- value: "history",
4903
- children: [
4904
- "Histórico (",
4905
- snapshot.history.length,
4906
- ")"
4907
- ]
4908
- })]
4909
- }), view === "history" && /* @__PURE__ */ jsx(Button, {
4910
- size: "small",
4911
- onClick: store.clearHistory,
4912
- children: "Limpar"
4913
- })]
5550
+ children: [
5551
+ /* @__PURE__ */ jsxs(ToggleButtonGroup, {
5552
+ size: "small",
5553
+ exclusive: true,
5554
+ value: view,
5555
+ onChange: (_, value) => value && setView(value),
5556
+ children: [/* @__PURE__ */ jsxs(ToggleButton, {
5557
+ value: "active",
5558
+ children: [
5559
+ "Ativas (",
5560
+ snapshot.instances.length,
5561
+ ")"
5562
+ ]
5563
+ }), /* @__PURE__ */ jsxs(ToggleButton, {
5564
+ value: "history",
5565
+ children: [
5566
+ "Histórico (",
5567
+ snapshot.history.length,
5568
+ ")"
5569
+ ]
5570
+ })]
5571
+ }),
5572
+ view === "history" && /* @__PURE__ */ jsx(Button, {
5573
+ size: "small",
5574
+ onClick: store.clearHistory,
5575
+ children: "Limpar"
5576
+ }),
5577
+ /* @__PURE__ */ jsx(TextField, {
5578
+ select: true,
5579
+ size: "small",
5580
+ value: sort,
5581
+ onChange: (e) => changeSort(e.target.value),
5582
+ title: "Ordenar a lista",
5583
+ sx: {
5584
+ ml: "auto",
5585
+ minWidth: 0,
5586
+ "& .MuiSelect-select": {
5587
+ fontSize: 12,
5588
+ py: .5
5589
+ }
5590
+ },
5591
+ children: Object.keys(SORT_LABEL).map((key) => /* @__PURE__ */ jsx(MenuItem, {
5592
+ value: key,
5593
+ dense: true,
5594
+ children: key === "default" && view === "history" ? "mais recentes" : SORT_LABEL[key]
5595
+ }, key))
5596
+ })
5597
+ ]
4914
5598
  }),
4915
5599
  /* @__PURE__ */ jsxs(Box, {
4916
5600
  sx: {
@@ -5102,6 +5786,7 @@ function RunDetail({ run, devtools, capabilities, mounted }) {
5102
5786
  },
5103
5787
  children: run.error
5104
5788
  }),
5789
+ run.status === "error" && !debug && /* @__PURE__ */ jsx(FailedSql, { run }),
5105
5790
  /* @__PURE__ */ jsx(Section, {
5106
5791
  title: "Consulta",
5107
5792
  children: /* @__PURE__ */ jsx(CodeBlock, {
@@ -5149,6 +5834,67 @@ function RunDetail({ run, devtools, capabilities, mounted }) {
5149
5834
  })
5150
5835
  ] });
5151
5836
  }
5837
+ /**
5838
+ * SQL de uma consulta que deu erro: o servidor não devolve debug junto com o erro, então pede só a
5839
+ * compilação (`devtools.compile`), sem executar. Se nem compilar, mostra por quê.
5840
+ */
5841
+ function FailedSql({ run }) {
5842
+ const { fetcher } = useDashboard();
5843
+ const [state, setState] = useState(null);
5844
+ useEffect(() => {
5845
+ let active = true;
5846
+ setState({ loading: true });
5847
+ fetcher({
5848
+ ...run.query,
5849
+ devtools: { compile: true }
5850
+ }).then((result) => {
5851
+ const debug = result.devtools?.debug;
5852
+ if (active) setState(debug ? {
5853
+ loading: false,
5854
+ data: debug
5855
+ } : {
5856
+ loading: false,
5857
+ error: "O endpoint não devolveu o SQL (precisa de runQuery(..., { devtools: true }) numa versão que aceite devtools.compile)."
5858
+ });
5859
+ }).catch((error) => active && setState({
5860
+ loading: false,
5861
+ error: errorMessage(error)
5862
+ }));
5863
+ return () => {
5864
+ active = false;
5865
+ };
5866
+ }, [fetcher, run.query]);
5867
+ return /* @__PURE__ */ jsx(Section, {
5868
+ title: "SQL gerado (não executou)",
5869
+ children: /* @__PURE__ */ jsx(ToolResult, {
5870
+ state,
5871
+ children: (debug) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(CodeBlock, {
5872
+ sql: true,
5873
+ code: inlineParams(debug.sql, debug.params)
5874
+ }), /* @__PURE__ */ jsxs(Typography, {
5875
+ sx: {
5876
+ fontSize: 11,
5877
+ color: "text.secondary",
5878
+ mt: .5
5879
+ },
5880
+ children: [
5881
+ "Compilado de novo agora",
5882
+ debug.draft ? ", com o rascunho atual" : "",
5883
+ ", para depurar o erro acima.",
5884
+ !!debug.params.length && /* @__PURE__ */ jsxs(Box, {
5885
+ component: "span",
5886
+ sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" },
5887
+ children: [
5888
+ " ",
5889
+ "params: ",
5890
+ JSON.stringify(debug.params)
5891
+ ]
5892
+ })
5893
+ ]
5894
+ })] })
5895
+ })
5896
+ });
5897
+ }
5152
5898
  function useTool() {
5153
5899
  const [state, setState] = useState(null);
5154
5900
  const run = async (load) => {
@@ -5632,9 +6378,10 @@ function CheckList({ checks, expectedLabel, actualLabel }) {
5632
6378
  * painel com as consultas da página, o modelo editável, um playground, regressão, dicionário e
5633
6379
  * filtros. Montado pelo próprio `DashboardProvider` em desenvolvimento.
5634
6380
  *
5635
- * Dois modos: fixado na parte de baixo da tela ou flutuante (janela que se arrasta pelo cabeçalho e
5636
- * redimensiona pelo canto). Fixado (embaixo, à esquerda ou à direita), o painel ocupa espaço: a
5637
- * página encolhe para não ficar escondida atrás dele; só o modo flutuante sobrepõe a página.
6381
+ * Modos: fixado (embaixo, à esquerda ou à direita), flutuante (janela que se arrasta pelo cabeçalho
6382
+ * e redimensiona pelo canto) ou numa janela separada do navegador (`DevtoolsWindow`). Fixado, o
6383
+ * painel ocupa espaço: a página encolhe para não ficar escondida atrás dele; só o modo flutuante
6384
+ * sobrepõe a página.
5638
6385
  * Atalhos: Alt+Shift+B abre/fecha o painel, Alt+Shift+F alterna entre fixado e flutuante,
5639
6386
  * Alt+Shift+I liga o inspetor, Esc sai do inspetor.
5640
6387
  */
@@ -5723,13 +6470,15 @@ function loadUi() {
5723
6470
  "bottom",
5724
6471
  "left",
5725
6472
  "right",
5726
- "floating"
6473
+ "floating",
6474
+ "window"
5727
6475
  ].includes(saved.mode)) saved.mode = "bottom";
5728
6476
  if (![
5729
6477
  "bottom",
5730
6478
  "left",
5731
6479
  "right"
5732
6480
  ].includes(saved.dock)) saved.dock = "bottom";
6481
+ if (saved.mode === "window") saved.open = false;
5733
6482
  return {
5734
6483
  ...saved,
5735
6484
  float: clampFloat({
@@ -5828,6 +6577,7 @@ function DashboardDevtools() {
5828
6577
  const [pickedItem, setPickedItem] = useState(null);
5829
6578
  const [hovered, setHovered] = useState(null);
5830
6579
  const [inspecting, setInspecting] = useState(false);
6580
+ const [popup, setPopup] = useState(null);
5831
6581
  const store = devtools?.store;
5832
6582
  const instances = useSyncExternalStore(store?.subscribe ?? (() => () => {}), () => store?.getSnapshot(), () => store?.getSnapshot())?.instances ?? [];
5833
6583
  useEffect(() => {
@@ -5886,18 +6636,85 @@ function DashboardDevtools() {
5886
6636
  }));
5887
6637
  }, [layoutTab])
5888
6638
  });
6639
+ const closePopup = useCallback(() => {
6640
+ setPopup((current) => {
6641
+ if (current && !current.closed) current.close();
6642
+ return null;
6643
+ });
6644
+ }, []);
6645
+ const openPanel = useCallback(() => {
6646
+ if (ui.mode !== "window") {
6647
+ setUi((current) => ({
6648
+ ...current,
6649
+ open: true
6650
+ }));
6651
+ return;
6652
+ }
6653
+ const opened = popup && !popup.closed ? popup : openDevtoolsWindow();
6654
+ if (opened) {
6655
+ opened.focus();
6656
+ setPopup(opened);
6657
+ setUi((current) => ({
6658
+ ...current,
6659
+ open: true
6660
+ }));
6661
+ } else setUi((current) => ({
6662
+ ...current,
6663
+ open: true,
6664
+ mode: current.dock
6665
+ }));
6666
+ }, [ui.mode, popup]);
6667
+ const closePanel = useCallback(() => {
6668
+ closePopup();
6669
+ setUi((current) => ({
6670
+ ...current,
6671
+ open: false
6672
+ }));
6673
+ }, [closePopup]);
6674
+ const toWindow = () => {
6675
+ const opened = openDevtoolsWindow();
6676
+ if (!opened) return;
6677
+ opened.focus();
6678
+ setPopup(opened);
6679
+ setUi((current) => ({
6680
+ ...current,
6681
+ open: true,
6682
+ mode: "window"
6683
+ }));
6684
+ };
6685
+ const onPopupClosed = useCallback(() => {
6686
+ setPopup(null);
6687
+ setUi((current) => ({
6688
+ ...current,
6689
+ open: false
6690
+ }));
6691
+ }, []);
6692
+ const keyActions = useRef({
6693
+ openPanel,
6694
+ closePanel,
6695
+ closePopup,
6696
+ open: ui.open,
6697
+ inspecting
6698
+ });
6699
+ keyActions.current = {
6700
+ openPanel,
6701
+ closePanel,
6702
+ closePopup,
6703
+ open: ui.open,
6704
+ inspecting
6705
+ };
5889
6706
  useEffect(() => {
5890
6707
  const onKey = (event) => {
5891
- if (event.key === "Escape" && inspecting) setInspecting(false);
6708
+ const actions = keyActions.current;
6709
+ if (event.key === "Escape" && actions.inspecting) setInspecting(false);
5892
6710
  if (!event.altKey || !event.shiftKey) return;
5893
6711
  if (event.code === "KeyB") {
5894
6712
  event.preventDefault();
5895
- setUi((current) => ({
5896
- ...current,
5897
- open: !current.open
5898
- }));
6713
+ if (actions.open) actions.closePanel();
6714
+ else actions.openPanel();
5899
6715
  } else if (event.code === "KeyF") {
5900
6716
  event.preventDefault();
6717
+ actions.closePopup();
5901
6718
  setUi((current) => ({
5902
6719
  ...current,
5903
6720
  open: true,
@@ -5908,10 +6725,13 @@ function DashboardDevtools() {
5908
6725
  setInspecting((value) => !value);
5909
6726
  }
5910
6727
  };
5911
- window.addEventListener("keydown", onKey);
5912
- return () => window.removeEventListener("keydown", onKey);
5913
- }, [inspecting]);
5914
- const reserved = ui.open && ui.mode !== "floating" ? ui.mode : null;
6728
+ const targets = popup && !popup.closed ? [window, popup] : [window];
6729
+ for (const target of targets) target.addEventListener("keydown", onKey);
6730
+ return () => {
6731
+ for (const target of targets) target.removeEventListener("keydown", onKey);
6732
+ };
6733
+ }, [popup]);
6734
+ const reserved = ui.open && ui.mode !== "floating" && ui.mode !== "window" ? ui.mode : null;
5915
6735
  const reservedSize = reserved === "bottom" ? ui.height : ui.width;
5916
6736
  const reservation = useRef(null);
5917
6737
  const reservedSizeRef = useRef(reservedSize);
@@ -5935,6 +6755,7 @@ function DashboardDevtools() {
5935
6755
  return () => window.removeEventListener("resize", onResize);
5936
6756
  }, []);
5937
6757
  if (!devtools || !store) return null;
6758
+ const inWindow = ui.mode === "window" && !!popup;
5938
6759
  const floating = ui.mode === "floating";
5939
6760
  const side = ui.mode === "left" || ui.mode === "right" ? ui.mode : null;
5940
6761
  const startDockResize = (event) => {
@@ -5971,15 +6792,21 @@ function DashboardDevtools() {
5971
6792
  })
5972
6793
  })));
5973
6794
  };
5974
- const toggleMode = () => setUi((current) => ({
5975
- ...current,
5976
- mode: current.mode === "floating" ? current.dock : "floating"
5977
- }));
5978
- const dockTo = (dock) => setUi((current) => ({
5979
- ...current,
5980
- mode: dock,
5981
- dock
5982
- }));
6795
+ const toggleMode = () => {
6796
+ closePopup();
6797
+ setUi((current) => ({
6798
+ ...current,
6799
+ mode: current.mode === "floating" ? current.dock : "floating"
6800
+ }));
6801
+ };
6802
+ const dockTo = (dock) => {
6803
+ closePopup();
6804
+ setUi((current) => ({
6805
+ ...current,
6806
+ mode: dock,
6807
+ dock
6808
+ }));
6809
+ };
5983
6810
  const clearServerCache = () => loadModel(true).then(devtools.refresh);
5984
6811
  const loading = instances.filter((item) => item.last.status === "loading").length;
5985
6812
  const errors = instances.filter((item) => item.last.status === "error").length;
@@ -6008,6 +6835,229 @@ function DashboardDevtools() {
6008
6835
  },
6009
6836
  children: /* @__PURE__ */ jsx(CircularProgress, { size: 20 })
6010
6837
  }) : content(original);
6838
+ const header = /* @__PURE__ */ jsxs(Box, {
6839
+ onMouseDown: startDrag,
6840
+ onDoubleClick: (event) => {
6841
+ if (floating && !event.target.closest("button, [role=\"tab\"]")) toggleMode();
6842
+ },
6843
+ sx: {
6844
+ display: "flex",
6845
+ alignItems: "center",
6846
+ gap: 1.5,
6847
+ px: 1.5,
6848
+ borderBottom: 1,
6849
+ borderColor: "divider",
6850
+ cursor: floating ? "move" : void 0,
6851
+ ...side && {
6852
+ flexWrap: "wrap",
6853
+ columnGap: 1,
6854
+ pt: .5
6855
+ }
6856
+ },
6857
+ children: [
6858
+ /* @__PURE__ */ jsx(Typography, {
6859
+ sx: {
6860
+ fontWeight: 800,
6861
+ color: "primary.main",
6862
+ whiteSpace: "nowrap"
6863
+ },
6864
+ children: "◆ BI Devtools"
6865
+ }),
6866
+ /* @__PURE__ */ jsx(Chip, {
6867
+ size: "small",
6868
+ label: model,
6869
+ sx: { fontFamily: MONO }
6870
+ }),
6871
+ /* @__PURE__ */ jsxs(Tabs, {
6872
+ value: ui.tab,
6873
+ onChange: (_, tab) => setTab(tab),
6874
+ variant: "scrollable",
6875
+ scrollButtons: "auto",
6876
+ sx: {
6877
+ minHeight: 40,
6878
+ ...side && {
6879
+ order: 1,
6880
+ width: "100%"
6881
+ }
6882
+ },
6883
+ children: [
6884
+ /* @__PURE__ */ jsx(Tab, {
6885
+ value: "queries",
6886
+ label: /* @__PURE__ */ jsxs(Box, {
6887
+ sx: {
6888
+ display: "flex",
6889
+ alignItems: "center",
6890
+ gap: .75
6891
+ },
6892
+ children: [
6893
+ "Consultas (",
6894
+ instances.length,
6895
+ ")",
6896
+ !!loading && /* @__PURE__ */ jsx(CircularProgress, { size: 10 }),
6897
+ !!(errors || storms) && /* @__PURE__ */ jsx(Chip, {
6898
+ size: "small",
6899
+ color: "error",
6900
+ label: errors + storms,
6901
+ sx: { height: 16 }
6902
+ })
6903
+ ]
6904
+ })
6905
+ }),
6906
+ /* @__PURE__ */ jsx(Tab, {
6907
+ value: "model",
6908
+ label: "Modelo"
6909
+ }),
6910
+ /* @__PURE__ */ jsx(Tab, {
6911
+ value: "layout",
6912
+ label: "Layout"
6913
+ }),
6914
+ /* @__PURE__ */ jsx(Tab, {
6915
+ value: "playground",
6916
+ label: "Playground"
6917
+ }),
6918
+ /* @__PURE__ */ jsx(Tab, {
6919
+ value: "regression",
6920
+ label: "Regressão"
6921
+ }),
6922
+ /* @__PURE__ */ jsx(Tab, {
6923
+ value: "dictionary",
6924
+ label: "Dicionário"
6925
+ }),
6926
+ /* @__PURE__ */ jsx(Tab, {
6927
+ value: "filters",
6928
+ label: "Filtros"
6929
+ })
6930
+ ]
6931
+ }),
6932
+ devtools.draft && /* @__PURE__ */ jsx(Chip, {
6933
+ size: "small",
6934
+ color: "warning",
6935
+ label: "rascunho aplicado"
6936
+ }),
6937
+ /* @__PURE__ */ jsxs(Box, {
6938
+ sx: {
6939
+ ml: "auto",
6940
+ display: "flex",
6941
+ alignItems: "center",
6942
+ gap: .5
6943
+ },
6944
+ children: [
6945
+ /* @__PURE__ */ jsx(Tooltip, {
6946
+ title: "Inspecionar componente (Alt+Shift+I)",
6947
+ children: /* @__PURE__ */ jsx(IconButton, {
6948
+ size: "small",
6949
+ color: inspecting ? "primary" : "default",
6950
+ onClick: () => setInspecting((value) => !value),
6951
+ "aria-label": "Inspecionar componente",
6952
+ children: "⌖"
6953
+ })
6954
+ }),
6955
+ DOCK_SIDES.map(({ side: dock, icon, label }) => /* @__PURE__ */ jsx(Tooltip, {
6956
+ title: `${label} (ocupa espaço na página)`,
6957
+ children: /* @__PURE__ */ jsx(IconButton, {
6958
+ size: "small",
6959
+ color: ui.mode === dock ? "primary" : "default",
6960
+ onClick: () => dockTo(dock),
6961
+ "aria-label": label,
6962
+ children: icon
6963
+ })
6964
+ }, dock)),
6965
+ /* @__PURE__ */ jsx(Tooltip, {
6966
+ title: "Janela flutuante, por cima da página (Alt+Shift+F) · arraste pelo cabeçalho",
6967
+ children: /* @__PURE__ */ jsx(IconButton, {
6968
+ size: "small",
6969
+ color: floating ? "primary" : "default",
6970
+ onClick: toggleMode,
6971
+ "aria-label": "Devtools flutuante",
6972
+ children: "⧉"
6973
+ })
6974
+ }),
6975
+ /* @__PURE__ */ jsx(Tooltip, {
6976
+ title: "Abrir numa janela separada do navegador (ex.: em outro monitor)",
6977
+ children: /* @__PURE__ */ jsx(IconButton, {
6978
+ size: "small",
6979
+ color: inWindow ? "primary" : "default",
6980
+ onClick: inWindow ? () => popup?.focus() : toWindow,
6981
+ "aria-label": "Devtools em janela separada",
6982
+ children: "↗"
6983
+ })
6984
+ }),
6985
+ /* @__PURE__ */ jsx(Tooltip, {
6986
+ title: "Fechar (Alt+Shift+B)",
6987
+ children: /* @__PURE__ */ jsx(IconButton, {
6988
+ size: "small",
6989
+ onClick: closePanel,
6990
+ "aria-label": "Fechar devtools",
6991
+ children: "✕"
6992
+ })
6993
+ })
6994
+ ]
6995
+ })
6996
+ ]
6997
+ });
6998
+ const body = /* @__PURE__ */ jsxs(Box, {
6999
+ sx: {
7000
+ flex: 1,
7001
+ minHeight: 0
7002
+ },
7003
+ children: [
7004
+ ui.tab === "queries" && /* @__PURE__ */ jsx(QueriesPanel, {
7005
+ devtools,
7006
+ capabilities,
7007
+ selected,
7008
+ onSelect: setSelected,
7009
+ onHover: setHovered,
7010
+ onClearServerCache: clearServerCache
7011
+ }),
7012
+ ui.tab === "model" && needsModel((registered) => /* @__PURE__ */ jsx(ModelPanel, {
7013
+ devtools,
7014
+ original: registered,
7015
+ capabilities,
7016
+ tables: tables ?? [],
7017
+ onTry: (selection) => {
7018
+ setPlayground(selection);
7019
+ setTab("playground");
7020
+ },
7021
+ onSaved: () => setTimeout(() => loadModel(), 1e3)
7022
+ })),
7023
+ ui.tab === "layout" && needsModel((registered) => /* @__PURE__ */ jsx(LayoutPanel, {
7024
+ devtools,
7025
+ model: devtools.draft ?? registered,
7026
+ capabilities,
7027
+ picked: pickedItem,
7028
+ onHover: setHovered
7029
+ })),
7030
+ ui.tab === "playground" && needsModel((registered) => /* @__PURE__ */ jsx(PlaygroundPanel, {
7031
+ model: devtools.draft ?? registered,
7032
+ selection: playground,
7033
+ onSelectionChange: setPlayground
7034
+ })),
7035
+ ui.tab === "regression" && /* @__PURE__ */ jsx(RegressionPanel, {
7036
+ devtools,
7037
+ capabilities
7038
+ }),
7039
+ ui.tab === "dictionary" && /* @__PURE__ */ jsxs(Box, {
7040
+ sx: {
7041
+ p: 1.5,
7042
+ overflow: "auto",
7043
+ height: "100%"
7044
+ },
7045
+ children: [/* @__PURE__ */ jsxs(Typography, {
7046
+ sx: {
7047
+ fontSize: 12,
7048
+ color: "text.secondary",
7049
+ mb: 1.5
7050
+ },
7051
+ children: [
7052
+ "O mesmo ",
7053
+ /* @__PURE__ */ jsx("code", { children: "<ModelDictionary />" }),
7054
+ " pode ir numa página do dashboard como documentação para quem usa. Edite rótulos e descrições na aba Modelo."
7055
+ ]
7056
+ }), /* @__PURE__ */ jsx(ModelDictionary, {})]
7057
+ }),
7058
+ ui.tab === "filters" && /* @__PURE__ */ jsx(FiltersPanel, { devtools })
7059
+ ]
7060
+ });
6011
7061
  return /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsxs(ThemeProvider, {
6012
7062
  theme,
6013
7063
  children: [
@@ -6016,7 +7066,26 @@ function DashboardDevtools() {
6016
7066
  label: hoveredLabel,
6017
7067
  zIndex: 1251
6018
7068
  }),
6019
- ui.open ? /* @__PURE__ */ jsxs(Paper, {
7069
+ ui.open && inWindow && popup && /* @__PURE__ */ jsx(DevtoolsWindow, {
7070
+ popup,
7071
+ title: `BI Devtools · ${model}`,
7072
+ theme,
7073
+ onClosed: onPopupClosed,
7074
+ children: /* @__PURE__ */ jsxs(Paper, {
7075
+ ...devtoolsMarker,
7076
+ square: true,
7077
+ elevation: 0,
7078
+ sx: {
7079
+ height: "100%",
7080
+ display: "flex",
7081
+ flexDirection: "column",
7082
+ color: "text.primary",
7083
+ overflow: "hidden"
7084
+ },
7085
+ children: [header, body]
7086
+ })
7087
+ }),
7088
+ ui.open && !inWindow ? /* @__PURE__ */ jsxs(Paper, {
6020
7089
  ...devtoolsMarker,
6021
7090
  square: !floating,
6022
7091
  elevation: floating ? 16 : 12,
@@ -6081,232 +7150,15 @@ function DashboardDevtools() {
6081
7150
  cursor: "ns-resize"
6082
7151
  }
6083
7152
  }),
6084
- /* @__PURE__ */ jsxs(Box, {
6085
- onMouseDown: startDrag,
6086
- onDoubleClick: (event) => {
6087
- if (floating && !event.target.closest("button, [role=\"tab\"]")) toggleMode();
6088
- },
6089
- sx: {
6090
- display: "flex",
6091
- alignItems: "center",
6092
- gap: 1.5,
6093
- px: 1.5,
6094
- borderBottom: 1,
6095
- borderColor: "divider",
6096
- cursor: floating ? "move" : void 0,
6097
- ...side && {
6098
- flexWrap: "wrap",
6099
- columnGap: 1,
6100
- pt: .5
6101
- }
6102
- },
6103
- children: [
6104
- /* @__PURE__ */ jsx(Typography, {
6105
- sx: {
6106
- fontWeight: 800,
6107
- color: "primary.main",
6108
- whiteSpace: "nowrap"
6109
- },
6110
- children: "◆ BI Devtools"
6111
- }),
6112
- /* @__PURE__ */ jsx(Chip, {
6113
- size: "small",
6114
- label: model,
6115
- sx: { fontFamily: MONO }
6116
- }),
6117
- /* @__PURE__ */ jsxs(Tabs, {
6118
- value: ui.tab,
6119
- onChange: (_, tab) => setTab(tab),
6120
- variant: "scrollable",
6121
- scrollButtons: "auto",
6122
- sx: {
6123
- minHeight: 40,
6124
- ...side && {
6125
- order: 1,
6126
- width: "100%"
6127
- }
6128
- },
6129
- children: [
6130
- /* @__PURE__ */ jsx(Tab, {
6131
- value: "queries",
6132
- label: /* @__PURE__ */ jsxs(Box, {
6133
- sx: {
6134
- display: "flex",
6135
- alignItems: "center",
6136
- gap: .75
6137
- },
6138
- children: [
6139
- "Consultas (",
6140
- instances.length,
6141
- ")",
6142
- !!loading && /* @__PURE__ */ jsx(CircularProgress, { size: 10 }),
6143
- !!(errors || storms) && /* @__PURE__ */ jsx(Chip, {
6144
- size: "small",
6145
- color: "error",
6146
- label: errors + storms,
6147
- sx: { height: 16 }
6148
- })
6149
- ]
6150
- })
6151
- }),
6152
- /* @__PURE__ */ jsx(Tab, {
6153
- value: "model",
6154
- label: "Modelo"
6155
- }),
6156
- /* @__PURE__ */ jsx(Tab, {
6157
- value: "layout",
6158
- label: "Layout"
6159
- }),
6160
- /* @__PURE__ */ jsx(Tab, {
6161
- value: "playground",
6162
- label: "Playground"
6163
- }),
6164
- /* @__PURE__ */ jsx(Tab, {
6165
- value: "regression",
6166
- label: "Regressão"
6167
- }),
6168
- /* @__PURE__ */ jsx(Tab, {
6169
- value: "dictionary",
6170
- label: "Dicionário"
6171
- }),
6172
- /* @__PURE__ */ jsx(Tab, {
6173
- value: "filters",
6174
- label: "Filtros"
6175
- })
6176
- ]
6177
- }),
6178
- devtools.draft && /* @__PURE__ */ jsx(Chip, {
6179
- size: "small",
6180
- color: "warning",
6181
- label: "rascunho aplicado"
6182
- }),
6183
- /* @__PURE__ */ jsxs(Box, {
6184
- sx: {
6185
- ml: "auto",
6186
- display: "flex",
6187
- alignItems: "center",
6188
- gap: .5
6189
- },
6190
- children: [
6191
- /* @__PURE__ */ jsx(Tooltip, {
6192
- title: "Inspecionar componente (Alt+Shift+I)",
6193
- children: /* @__PURE__ */ jsx(IconButton, {
6194
- size: "small",
6195
- color: inspecting ? "primary" : "default",
6196
- onClick: () => setInspecting((value) => !value),
6197
- "aria-label": "Inspecionar componente",
6198
- children: "⌖"
6199
- })
6200
- }),
6201
- DOCK_SIDES.map(({ side: dock, icon, label }) => /* @__PURE__ */ jsx(Tooltip, {
6202
- title: `${label} (ocupa espaço na página)`,
6203
- children: /* @__PURE__ */ jsx(IconButton, {
6204
- size: "small",
6205
- color: ui.mode === dock ? "primary" : "default",
6206
- onClick: () => dockTo(dock),
6207
- "aria-label": label,
6208
- children: icon
6209
- })
6210
- }, dock)),
6211
- /* @__PURE__ */ jsx(Tooltip, {
6212
- title: "Janela flutuante, por cima da página (Alt+Shift+F) · arraste pelo cabeçalho",
6213
- children: /* @__PURE__ */ jsx(IconButton, {
6214
- size: "small",
6215
- color: floating ? "primary" : "default",
6216
- onClick: toggleMode,
6217
- "aria-label": "Devtools flutuante",
6218
- children: "⧉"
6219
- })
6220
- }),
6221
- /* @__PURE__ */ jsx(Tooltip, {
6222
- title: "Fechar (Alt+Shift+B)",
6223
- children: /* @__PURE__ */ jsx(IconButton, {
6224
- size: "small",
6225
- onClick: () => setUi((current) => ({
6226
- ...current,
6227
- open: false
6228
- })),
6229
- "aria-label": "Fechar devtools",
6230
- children: "✕"
6231
- })
6232
- })
6233
- ]
6234
- })
6235
- ]
6236
- }),
6237
- /* @__PURE__ */ jsxs(Box, {
6238
- sx: {
6239
- flex: 1,
6240
- minHeight: 0
6241
- },
6242
- children: [
6243
- ui.tab === "queries" && /* @__PURE__ */ jsx(QueriesPanel, {
6244
- devtools,
6245
- capabilities,
6246
- selected,
6247
- onSelect: setSelected,
6248
- onHover: setHovered,
6249
- onClearServerCache: clearServerCache
6250
- }),
6251
- ui.tab === "model" && needsModel((registered) => /* @__PURE__ */ jsx(ModelPanel, {
6252
- devtools,
6253
- original: registered,
6254
- capabilities,
6255
- tables: tables ?? [],
6256
- onTry: (selection) => {
6257
- setPlayground(selection);
6258
- setTab("playground");
6259
- },
6260
- onSaved: () => setTimeout(() => loadModel(), 1e3)
6261
- })),
6262
- ui.tab === "layout" && needsModel((registered) => /* @__PURE__ */ jsx(LayoutPanel, {
6263
- devtools,
6264
- model: devtools.draft ?? registered,
6265
- capabilities,
6266
- picked: pickedItem,
6267
- onHover: setHovered
6268
- })),
6269
- ui.tab === "playground" && needsModel((registered) => /* @__PURE__ */ jsx(PlaygroundPanel, {
6270
- model: devtools.draft ?? registered,
6271
- selection: playground,
6272
- onSelectionChange: setPlayground
6273
- })),
6274
- ui.tab === "regression" && /* @__PURE__ */ jsx(RegressionPanel, {
6275
- devtools,
6276
- capabilities
6277
- }),
6278
- ui.tab === "dictionary" && /* @__PURE__ */ jsxs(Box, {
6279
- sx: {
6280
- p: 1.5,
6281
- overflow: "auto",
6282
- height: "100%"
6283
- },
6284
- children: [/* @__PURE__ */ jsxs(Typography, {
6285
- sx: {
6286
- fontSize: 12,
6287
- color: "text.secondary",
6288
- mb: 1.5
6289
- },
6290
- children: [
6291
- "O mesmo ",
6292
- /* @__PURE__ */ jsx("code", { children: "<ModelDictionary />" }),
6293
- " pode ir numa página do dashboard como documentação para quem usa. Edite rótulos e descrições na aba Modelo."
6294
- ]
6295
- }), /* @__PURE__ */ jsx(ModelDictionary, {})]
6296
- }),
6297
- ui.tab === "filters" && /* @__PURE__ */ jsx(FiltersPanel, { devtools })
6298
- ]
6299
- })
7153
+ header,
7154
+ body
6300
7155
  ]
6301
7156
  }) : /* @__PURE__ */ jsx(ToggleButton$1, {
6302
7157
  draft: !!devtools.draft,
6303
7158
  loading,
6304
7159
  problems: errors + storms,
6305
7160
  inspecting,
6306
- onOpen: () => setUi((current) => ({
6307
- ...current,
6308
- open: true
6309
- }))
7161
+ onOpen: inWindow ? () => popup?.focus() : openPanel
6310
7162
  }),
6311
7163
  inspecting && /* @__PURE__ */ jsx(Box, {
6312
7164
  ...devtoolsMarker,
@@ -6394,4 +7246,4 @@ function ToggleButton$1({ draft, loading, problems, inspecting, onOpen }) {
6394
7246
  //#endregion
6395
7247
  export { DashboardDevtools as default };
6396
7248
 
6397
- //# sourceMappingURL=DashboardDevtools-5wh3brAT.js.map
7249
+ //# sourceMappingURL=DashboardDevtools-C8AXaFgw.js.map