@malloydata/malloy-explorer 0.0.278-dev250516210719 → 0.0.282-dev250528154617

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/esm/index.js CHANGED
@@ -17,7 +17,7 @@ import { jsx, Fragment, jsxs } from "react/jsx-runtime";
17
17
  import * as React from "react";
18
18
  import React__default, { useLayoutEffect, useEffect, useMemo, useState, useRef, createElement, useContext, useCallback, createContext, memo, useReducer } from "react";
19
19
  import * as QB from "@malloydata/malloy-query-builder";
20
- import { ASTArrowQueryDefinition, ASTSegmentViewDefinition, ASTRefinementViewDefinition, ASTOrderByViewOperation, ASTGroupByViewOperation, ASTAggregateViewOperation, ASTLimitViewOperation, ASTNestViewOperation, ASTTimeTruncationExpression, ASTWhereViewOperation, ASTHavingViewOperation, ASTArrowViewDefinition } from "@malloydata/malloy-query-builder";
20
+ import { ASTArrowQueryDefinition, ASTArrowViewDefinition, ASTSegmentViewDefinition, ASTNestViewOperation, ASTRefinementViewDefinition, ASTFilterWithLiteralEquality, ASTOrderByViewOperation, ASTGroupByViewOperation, ASTAggregateViewOperation, ASTLimitViewOperation, ASTTimeTruncationExpression, ASTFilterWithFilterString, ASTWhereViewOperation, ASTHavingViewOperation, ASTDrillViewOperation, ASTQuery } from "@malloydata/malloy-query-builder";
21
21
  import { Tag } from "@malloydata/malloy-tag";
22
22
  import { TemporalFilterExpression, BooleanFilterExpression, NumberFilterExpression, StringFilterExpression } from "@malloydata/malloy-filter";
23
23
  import "@malloydata/render/webcomponent";
@@ -3324,29 +3324,105 @@ function useQueryBuilder(source, query) {
3324
3324
  }
3325
3325
  }, [source, query]);
3326
3326
  }
3327
+ const MalloyQueryFocusContext = /* @__PURE__ */ React.createContext({
3328
+ focusMainView: () => {
3329
+ },
3330
+ isMainViewFocused: true,
3331
+ focusNestView: () => {
3332
+ },
3333
+ isNestViewFocused: () => false,
3334
+ focusedNestView: null
3335
+ });
3336
+ function MalloyQueryFocusProvider({
3337
+ rootQuery,
3338
+ focusedNestViewPath,
3339
+ onFocusedNestViewPathChange,
3340
+ children
3341
+ }) {
3342
+ const focusedNestView = React.useMemo(() => {
3343
+ if (focusedNestViewPath.length === 0) {
3344
+ return null;
3345
+ }
3346
+ if (rootQuery) {
3347
+ const queryDef = rootQuery.definition;
3348
+ if (queryDef instanceof ASTArrowQueryDefinition) {
3349
+ return findNestView(queryDef.view, [...focusedNestViewPath].reverse());
3350
+ }
3351
+ }
3352
+ return null;
3353
+ }, [rootQuery, focusedNestViewPath]);
3354
+ const focusMainView = React.useCallback(() => {
3355
+ onFocusedNestViewPathChange([]);
3356
+ }, [onFocusedNestViewPathChange]);
3357
+ const focusNestView = React.useCallback((path) => {
3358
+ onFocusedNestViewPathChange([...path]);
3359
+ }, [onFocusedNestViewPathChange]);
3360
+ const isNestViewFocused = React.useCallback((path) => {
3361
+ return JSON.stringify(path) === JSON.stringify(focusedNestViewPath);
3362
+ }, [focusedNestViewPath]);
3363
+ const isMainViewFocused = focusedNestViewPath.length === 0;
3364
+ return /* @__PURE__ */ jsx(MalloyQueryFocusContext.Provider, {
3365
+ value: {
3366
+ focusMainView,
3367
+ isMainViewFocused,
3368
+ focusNestView,
3369
+ isNestViewFocused,
3370
+ focusedNestView
3371
+ },
3372
+ children
3373
+ });
3374
+ }
3375
+ function useQueryFocus() {
3376
+ return React.useContext(MalloyQueryFocusContext);
3377
+ }
3378
+ const findNestView = (currentView, remainingPath) => {
3379
+ if (remainingPath.length === 0) {
3380
+ return null;
3381
+ }
3382
+ if (currentView instanceof ASTArrowViewDefinition) {
3383
+ return findNestView(currentView.view, remainingPath);
3384
+ }
3385
+ if (currentView instanceof ASTSegmentViewDefinition) {
3386
+ const currentNestName = remainingPath.pop();
3387
+ const currentNestOperation = currentView.operations.items.find((operation) => operation instanceof ASTNestViewOperation && operation.name === currentNestName);
3388
+ if (currentNestOperation === void 0) {
3389
+ remainingPath.push(currentNestName);
3390
+ return null;
3391
+ } else if (remainingPath.length === 0) {
3392
+ return currentNestOperation.view;
3393
+ } else {
3394
+ return findNestView(currentNestOperation.view.definition, remainingPath);
3395
+ }
3396
+ }
3397
+ if (currentView instanceof ASTRefinementViewDefinition) {
3398
+ return findNestView(currentView.refinement, remainingPath);
3399
+ }
3400
+ return null;
3401
+ };
3327
3402
  function MalloyExplorerProvider({
3328
3403
  source,
3329
3404
  query,
3330
- setQuery,
3405
+ onQueryChange,
3406
+ focusedNestViewPath,
3407
+ onFocusedNestViewPathChange,
3331
3408
  children,
3332
3409
  topValues
3333
3410
  }) {
3334
3411
  const rootQuery = useQueryBuilder(source, query);
3335
- const [currentNestView, setCurrentNestView] = React.useState(null);
3336
- const [currentNestQueryPanel, setCurrentNestQueryPanel] = React.useState(null);
3337
3412
  return /* @__PURE__ */ jsx(TooltipProvider, {
3338
- children: /* @__PURE__ */ jsx(QueryEditorContext.Provider, {
3339
- value: {
3340
- source,
3341
- rootQuery,
3342
- setQuery,
3343
- topValues,
3344
- currentNestQueryPanel,
3345
- onCurrentNestQueryPanelChange: setCurrentNestQueryPanel,
3346
- currentNestView,
3347
- onCurrentNestViewChange: setCurrentNestView
3348
- },
3349
- children
3413
+ children: /* @__PURE__ */ jsx(MalloyQueryFocusProvider, {
3414
+ rootQuery,
3415
+ focusedNestViewPath,
3416
+ onFocusedNestViewPathChange,
3417
+ children: /* @__PURE__ */ jsx(QueryEditorContext.Provider, {
3418
+ value: {
3419
+ source,
3420
+ rootQuery,
3421
+ setQuery: onQueryChange,
3422
+ topValues
3423
+ },
3424
+ children
3425
+ })
3350
3426
  })
3351
3427
  });
3352
3428
  }
@@ -25863,13 +25939,14 @@ function QueryActionBar({
25863
25939
  const {
25864
25940
  rootQuery,
25865
25941
  setQuery,
25866
- source,
25867
- onCurrentNestQueryPanelChange,
25868
- onCurrentNestViewChange
25942
+ source
25869
25943
  } = useContext(QueryEditorContext);
25870
25944
  const {
25871
25945
  onCollapse
25872
25946
  } = useContext(ResizableCollapsiblePanelContext);
25947
+ const {
25948
+ focusMainView
25949
+ } = useQueryFocus();
25873
25950
  const isQueryEmpty = !rootQuery || rootQuery.isEmpty();
25874
25951
  const isRunEnabled = rootQuery == null ? void 0 : rootQuery.isRunnable();
25875
25952
  const onRunQuery = () => {
@@ -25877,10 +25954,6 @@ function QueryActionBar({
25877
25954
  runQuery(source, rootQuery.build());
25878
25955
  }
25879
25956
  };
25880
- const focusMainQueryPanel = () => {
25881
- onCurrentNestViewChange == null ? void 0 : onCurrentNestViewChange(null);
25882
- onCurrentNestQueryPanelChange == null ? void 0 : onCurrentNestQueryPanelChange(null);
25883
- };
25884
25957
  return /* @__PURE__ */ jsxs("div", {
25885
25958
  ...{
25886
25959
  className: "mly78zum5 mly1qughib mly6s0dn4 mlye8ttls"
@@ -25901,7 +25974,7 @@ function QueryActionBar({
25901
25974
  },
25902
25975
  children: [/* @__PURE__ */ jsx(Button, {
25903
25976
  onClick: () => {
25904
- focusMainQueryPanel();
25977
+ focusMainView();
25905
25978
  setQuery == null ? void 0 : setQuery(void 0);
25906
25979
  },
25907
25980
  isDisabled: !rootQuery || (rootQuery == null ? void 0 : rootQuery.isEmpty()),
@@ -26027,6 +26100,85 @@ const styles$s = {
26027
26100
  $$css: true
26028
26101
  }
26029
26102
  };
26103
+ function LiteralValue({
26104
+ value,
26105
+ customStyle
26106
+ }) {
26107
+ if (!value) {
26108
+ return /* @__PURE__ */ jsx(Token, {
26109
+ label: "∅"
26110
+ });
26111
+ }
26112
+ switch (value.kind) {
26113
+ case "boolean_literal":
26114
+ return /* @__PURE__ */ jsx(Token, {
26115
+ label: value.boolean_value ? "true" : "false",
26116
+ customStyle
26117
+ });
26118
+ case "date_literal":
26119
+ case "timestamp_literal":
26120
+ return /* @__PURE__ */ jsx(Token, {
26121
+ label: "TODO",
26122
+ customStyle
26123
+ });
26124
+ case "null_literal":
26125
+ return /* @__PURE__ */ jsx(Token, {
26126
+ label: "∅"
26127
+ });
26128
+ case "number_literal":
26129
+ return /* @__PURE__ */ jsx(Token, {
26130
+ label: value.number_value.toLocaleString(),
26131
+ customStyle
26132
+ });
26133
+ case "string_literal":
26134
+ return /* @__PURE__ */ jsx(Token, {
26135
+ label: value.string_value,
26136
+ customStyle
26137
+ });
26138
+ case "filter_expression_literal":
26139
+ return /* @__PURE__ */ jsx(Token, {
26140
+ label: value.filter_expression_value,
26141
+ customStyle
26142
+ });
26143
+ }
26144
+ }
26145
+ function DrillOperations({
26146
+ drills
26147
+ }) {
26148
+ if (!drills.length) {
26149
+ return null;
26150
+ }
26151
+ return /* @__PURE__ */ jsxs("div", {
26152
+ children: [/* @__PURE__ */ jsx("div", {
26153
+ ..._stylex.props(styles$s.title),
26154
+ children: "drills"
26155
+ }), /* @__PURE__ */ jsx("div", {
26156
+ ...{
26157
+ className: "mly78zum5 mlydt5ytf mly1jnr06f"
26158
+ },
26159
+ children: drills.map((drill, key2) => /* @__PURE__ */ jsxs(TokenGroup, {
26160
+ color: "cyan",
26161
+ customStyle: localStyles.tokenGroup,
26162
+ children: [/* @__PURE__ */ jsx(Token, {
26163
+ label: drill.filter.fieldReference.name,
26164
+ icon: "filter"
26165
+ }), /* @__PURE__ */ jsx(Token, {
26166
+ label: "="
26167
+ }), drill.filter instanceof ASTFilterWithLiteralEquality ? /* @__PURE__ */ jsx(LiteralValue, {
26168
+ value: drill.filter.value.node
26169
+ }) : /* @__PURE__ */ jsx(Token, {
26170
+ label: drill.filter.filterString
26171
+ })]
26172
+ }, key2))
26173
+ })]
26174
+ });
26175
+ }
26176
+ const localStyles = {
26177
+ tokenGroup: {
26178
+ display: "mly78zum5",
26179
+ $$css: true
26180
+ }
26181
+ };
26030
26182
  function useCombinedRefs() {
26031
26183
  for (var _len = arguments.length, refs = new Array(_len), _key = 0; _key < _len; _key++) {
26032
26184
  refs[_key] = arguments[_key];
@@ -32401,7 +32553,9 @@ function getOutputNameToInputNameMap(segment) {
32401
32553
  for (const operation of segment.operations.items) {
32402
32554
  if (operation instanceof ASTGroupByViewOperation || operation instanceof ASTAggregateViewOperation) {
32403
32555
  const reference = operation.field.getReference();
32404
- nameMap.set(operation.name, toFullName(reference.path, reference.name));
32556
+ if (reference) {
32557
+ nameMap.set(operation.name, toFullName(reference.path, reference.name));
32558
+ }
32405
32559
  }
32406
32560
  }
32407
32561
  return nameMap;
@@ -32423,8 +32577,11 @@ function segmentHasFieldInOutputSpace(segment, path, name) {
32423
32577
  const match = segment.operations.items.find((operation) => {
32424
32578
  if (operation instanceof ASTGroupByViewOperation || operation instanceof ASTAggregateViewOperation) {
32425
32579
  const reference = operation.field.getReference();
32426
- const isEqual = areReferencesEqual(path, name, reference.path, reference.name);
32427
- return isEqual;
32580
+ if (reference) {
32581
+ return areReferencesEqual(path, name, reference.path, reference.name);
32582
+ } else {
32583
+ return false;
32584
+ }
32428
32585
  }
32429
32586
  return false;
32430
32587
  });
@@ -33141,11 +33298,12 @@ function SortableOperation({
33141
33298
  operation,
33142
33299
  color
33143
33300
  }) {
33301
+ var _a2;
33144
33302
  const {
33145
33303
  setQuery
33146
33304
  } = useContext(QueryEditorContext);
33147
33305
  const fieldInfo = operation.getFieldInfo();
33148
- const path = operation.field.getReference().path ?? NULL_PATH;
33306
+ const path = ((_a2 = operation.field.getReference()) == null ? void 0 : _a2.path) ?? NULL_PATH;
33149
33307
  const {
33150
33308
  attributes,
33151
33309
  listeners,
@@ -33346,28 +33504,62 @@ const parsedToLabels = (parsed, filterString) => {
33346
33504
  value = stringClause.escaped_values.join(", ");
33347
33505
  break;
33348
33506
  case "=":
33349
- op = "is";
33350
- value = stringClause.values.join(", ");
33507
+ {
33508
+ const {
33509
+ not,
33510
+ values
33511
+ } = stringClause;
33512
+ op = not ? "is not" : "is";
33513
+ value = values.join(", ");
33514
+ }
33351
33515
  break;
33352
33516
  case "contains":
33353
- op = "contains";
33354
- value = stringClause.values.join(", ");
33517
+ {
33518
+ const {
33519
+ not,
33520
+ values
33521
+ } = stringClause;
33522
+ op = not ? "does not contain" : "contains";
33523
+ value = values.join(", ");
33524
+ }
33355
33525
  break;
33356
33526
  case "starts":
33357
- op = "starts with";
33358
- value = stringClause.values.join(", ");
33527
+ {
33528
+ const {
33529
+ not,
33530
+ values
33531
+ } = stringClause;
33532
+ op = not ? "does not start with" : "starts with";
33533
+ value = values.join(", ");
33534
+ }
33359
33535
  break;
33360
33536
  case "ends":
33361
- op = "is like";
33362
- value = stringClause.values.join(", ");
33537
+ {
33538
+ const {
33539
+ not,
33540
+ values
33541
+ } = stringClause;
33542
+ op = not ? "does not end with" : "ends with";
33543
+ value = values.join(", ");
33544
+ }
33363
33545
  break;
33364
33546
  case "empty":
33365
- op = "is empty";
33366
- value = "";
33547
+ {
33548
+ const {
33549
+ not
33550
+ } = stringClause;
33551
+ op = not ? "is not empty" : "is empty";
33552
+ value = "";
33553
+ }
33367
33554
  break;
33368
33555
  case "null":
33369
- op = stringClause.not ? "is not" : "is";
33370
- value = "null";
33556
+ {
33557
+ const {
33558
+ not
33559
+ } = stringClause;
33560
+ op = not ? "is not" : "is";
33561
+ value = "null";
33562
+ }
33371
33563
  break;
33372
33564
  }
33373
33565
  }
@@ -33531,22 +33723,27 @@ function SingleFilterOperation({
33531
33723
  rootQuery,
33532
33724
  filterOperation
33533
33725
  }) {
33726
+ const {
33727
+ setQuery
33728
+ } = useContext(QueryEditorContext);
33729
+ const setFilter = useCallback((filter2) => {
33730
+ if (filterOperation.filter instanceof ASTFilterWithFilterString) {
33731
+ filterOperation.filter.setFilter(filter2);
33732
+ }
33733
+ setQuery == null ? void 0 : setQuery(rootQuery.build());
33734
+ }, [filterOperation.filter, rootQuery, setQuery]);
33735
+ if (!(filterOperation.filter instanceof ASTFilterWithFilterString)) {
33736
+ return null;
33737
+ }
33534
33738
  const {
33535
33739
  fieldReference,
33536
33740
  filterString
33537
33741
  } = filterOperation.filter;
33538
33742
  const filter = filterOperation.filter.getFilter();
33539
33743
  const fieldInfo = fieldReference.getFieldInfo();
33540
- const {
33541
- setQuery
33542
- } = useContext(QueryEditorContext);
33543
33744
  if (fieldInfo.kind !== "dimension" && fieldInfo.kind !== "measure") {
33544
33745
  throw new Error(`Invalid filter field kind: ${fieldInfo.kind}`);
33545
33746
  }
33546
- const setFilter = useCallback((filter2) => {
33547
- filterOperation.filter.setFilter(filter2);
33548
- setQuery == null ? void 0 : setQuery(rootQuery.build());
33549
- }, [filterOperation.filter, rootQuery, setQuery]);
33550
33747
  const {
33551
33748
  op,
33552
33749
  value
@@ -34157,6 +34354,35 @@ const styles$d = {
34157
34354
  $$css: true
34158
34355
  }
34159
34356
  };
34357
+ const NestViewPathContext = /* @__PURE__ */ React__default.createContext([]);
34358
+ function FocusableView({
34359
+ children,
34360
+ nest
34361
+ }) {
34362
+ const {
34363
+ focusNestView,
34364
+ focusMainView
34365
+ } = useQueryFocus();
34366
+ const parentNestViewPath = useContext(NestViewPathContext);
34367
+ return /* @__PURE__ */ jsx("div", {
34368
+ onPointerDown: (e) => {
34369
+ e.stopPropagation();
34370
+ if (nest) {
34371
+ focusNestView([...parentNestViewPath, nest.name]);
34372
+ } else {
34373
+ focusMainView();
34374
+ }
34375
+ },
34376
+ children: nest ? /* @__PURE__ */ jsx(NestViewPathContext.Provider, {
34377
+ value: [...parentNestViewPath, nest.name],
34378
+ children: /* @__PURE__ */ jsx("div", {
34379
+ children
34380
+ })
34381
+ }) : /* @__PURE__ */ jsx("div", {
34382
+ children
34383
+ })
34384
+ });
34385
+ }
34160
34386
  function NestOperations({
34161
34387
  rootQuery,
34162
34388
  view,
@@ -34180,31 +34406,14 @@ function NestOperation({
34180
34406
  nest
34181
34407
  }) {
34182
34408
  const {
34183
- setQuery,
34184
- currentNestQueryPanel,
34185
- onCurrentNestQueryPanelChange,
34186
- onCurrentNestViewChange
34409
+ setQuery
34187
34410
  } = useContext(QueryEditorContext);
34188
34411
  const [renameOpen, setRenameOpen] = useState(false);
34189
- const panelRef = React.useRef(null);
34190
- const isCurrentNestQueryPanelFocused = currentNestQueryPanel !== null && panelRef.current == currentNestQueryPanel;
34191
- React.useEffect(() => {
34192
- if (isCurrentNestQueryPanelFocused) {
34193
- onCurrentNestViewChange == null ? void 0 : onCurrentNestViewChange(nest.view);
34194
- }
34195
- }, [nest, isCurrentNestQueryPanelFocused, onCurrentNestViewChange]);
34196
- const focusCurrentNestQueryPanel = () => {
34197
- onCurrentNestQueryPanelChange == null ? void 0 : onCurrentNestQueryPanelChange(panelRef.current);
34198
- onCurrentNestViewChange == null ? void 0 : onCurrentNestViewChange(nest.view);
34199
- };
34200
- const focusParentQueryPanel = () => {
34201
- const currentPanel = panelRef.current;
34202
- const parent = findParentNestQueryPanel(currentPanel);
34203
- onCurrentNestQueryPanelChange == null ? void 0 : onCurrentNestQueryPanelChange(parent);
34204
- if (parent === null) {
34205
- onCurrentNestViewChange == null ? void 0 : onCurrentNestViewChange(null);
34206
- }
34207
- };
34412
+ const parentNestViewPath = useContext(NestViewPathContext);
34413
+ const {
34414
+ focusNestView,
34415
+ isNestViewFocused
34416
+ } = useQueryFocus();
34208
34417
  const getControls = (nest2) => /* @__PURE__ */ jsxs(Fragment, {
34209
34418
  children: [/* @__PURE__ */ jsxs(DropdownMenu, {
34210
34419
  trigger: /* @__PURE__ */ jsx(Button, {
@@ -34217,7 +34426,7 @@ function NestOperation({
34217
34426
  icon: "clear",
34218
34427
  label: "Delete Query",
34219
34428
  onClick: () => {
34220
- focusParentQueryPanel();
34429
+ focusNestView([...parentNestViewPath]);
34221
34430
  nest2.delete();
34222
34431
  setQuery == null ? void 0 : setQuery(rootQuery.build());
34223
34432
  }
@@ -34232,21 +34441,19 @@ function NestOperation({
34232
34441
  view: nest2.view
34233
34442
  })]
34234
34443
  });
34235
- return /* @__PURE__ */ jsx("div", {
34236
- ...{
34237
- className: "mlyj3b58b mly1yf7rl7 mly1xmf6yo mlyh8yej3"
34238
- },
34444
+ return /* @__PURE__ */ jsx(FocusableView, {
34445
+ nest,
34239
34446
  children: /* @__PURE__ */ jsxs("div", {
34240
- ref: panelRef,
34241
- onPointerDownCapture: focusCurrentNestQueryPanel,
34242
- "data-nest-panel": true,
34447
+ ...{
34448
+ className: "mlyj3b58b mly1yf7rl7 mly1xmf6yo mlyh8yej3"
34449
+ },
34243
34450
  children: [/* @__PURE__ */ jsx(CollapsiblePanel, {
34244
34451
  title: nest.name,
34245
34452
  icon: viewToVisualizationIcon(nest.view),
34246
34453
  defaultOpen: true,
34247
34454
  controls: getControls(nest),
34248
34455
  collapsedControls: getControls(nest),
34249
- isFocused: isCurrentNestQueryPanelFocused,
34456
+ isFocused: isNestViewFocused([...parentNestViewPath, nest.name]),
34250
34457
  children: /* @__PURE__ */ jsx(View, {
34251
34458
  rootQuery,
34252
34459
  view: nest.view
@@ -34258,14 +34465,8 @@ function NestOperation({
34258
34465
  open: renameOpen,
34259
34466
  setOpen: setRenameOpen
34260
34467
  })]
34261
- })
34262
- }, nest.name);
34263
- }
34264
- function findParentNestQueryPanel(element2) {
34265
- if (!element2 || !element2.parentElement) return null;
34266
- const parentElement = element2.parentElement;
34267
- if (parentElement.dataset.nestPanel !== void 0) return parentElement;
34268
- return findParentNestQueryPanel(parentElement);
34468
+ }, nest.name)
34469
+ });
34269
34470
  }
34270
34471
  function Operations({
34271
34472
  rootQuery,
@@ -34274,6 +34475,7 @@ function Operations({
34274
34475
  }) {
34275
34476
  const groupBys = [];
34276
34477
  const aggregates = [];
34478
+ const drills = [];
34277
34479
  const filters = [];
34278
34480
  const orderBys = [];
34279
34481
  const nests = [];
@@ -34295,6 +34497,8 @@ function Operations({
34295
34497
  orderBys.push(operation);
34296
34498
  } else if (operation instanceof ASTNestViewOperation) {
34297
34499
  nests.push(operation);
34500
+ } else if (operation instanceof ASTDrillViewOperation) {
34501
+ drills.push(operation);
34298
34502
  } else {
34299
34503
  limit = operation;
34300
34504
  }
@@ -34313,6 +34517,9 @@ function Operations({
34313
34517
  segment,
34314
34518
  view,
34315
34519
  aggregates
34520
+ }), /* @__PURE__ */ jsx(DrillOperations, {
34521
+ rootQuery,
34522
+ drills
34316
34523
  }), /* @__PURE__ */ jsx(FilterOperations, {
34317
34524
  rootQuery,
34318
34525
  filters
@@ -34434,19 +34641,13 @@ function Query({
34434
34641
  setQuery
34435
34642
  }) {
34436
34643
  const {
34437
- currentNestQueryPanel,
34438
- onCurrentNestQueryPanelChange,
34439
- onCurrentNestViewChange
34440
- } = React.useContext(QueryEditorContext);
34441
- const focusMainQueryPanel = () => {
34442
- onCurrentNestQueryPanelChange == null ? void 0 : onCurrentNestQueryPanelChange(null);
34443
- onCurrentNestViewChange == null ? void 0 : onCurrentNestViewChange(null);
34444
- };
34445
- return /* @__PURE__ */ jsx("div", {
34446
- onPointerDownCapture: focusMainQueryPanel,
34644
+ focusMainView,
34645
+ isMainViewFocused
34646
+ } = useQueryFocus();
34647
+ return /* @__PURE__ */ jsx(FocusableView, {
34447
34648
  children: /* @__PURE__ */ jsxs(CollapsiblePanel, {
34448
34649
  title: "Main query",
34449
- isFocused: !currentNestQueryPanel,
34650
+ isFocused: isMainViewFocused,
34450
34651
  controls: /* @__PURE__ */ jsxs(Fragment, {
34451
34652
  children: [/* @__PURE__ */ jsx(DropdownMenu, {
34452
34653
  trigger: /* @__PURE__ */ jsx(Button, {
@@ -34460,7 +34661,7 @@ function Query({
34460
34661
  icon: "clear",
34461
34662
  label: "Clear query",
34462
34663
  onClick: () => {
34463
- focusMainQueryPanel();
34664
+ focusMainView();
34464
34665
  setQuery == null ? void 0 : setQuery(void 0);
34465
34666
  },
34466
34667
  disabled: rootQuery.isEmpty()
@@ -35341,6 +35542,7 @@ const styles$5 = {
35341
35542
  }
35342
35543
  };
35343
35544
  function ResultDisplay({
35545
+ source,
35344
35546
  query
35345
35547
  }) {
35346
35548
  let displayComponent;
@@ -35374,6 +35576,7 @@ function ResultDisplay({
35374
35576
  break;
35375
35577
  case "finished":
35376
35578
  displayComponent = /* @__PURE__ */ jsx(ResponseDisplay, {
35579
+ source,
35377
35580
  response: query.response
35378
35581
  });
35379
35582
  break;
@@ -35381,6 +35584,7 @@ function ResultDisplay({
35381
35584
  return displayComponent;
35382
35585
  }
35383
35586
  function ResponseDisplay({
35587
+ source,
35384
35588
  response
35385
35589
  }) {
35386
35590
  let messageComponent = null;
@@ -35406,7 +35610,8 @@ function ResponseDisplay({
35406
35610
  children: [(response == null ? void 0 : response.runInfo) && /* @__PURE__ */ jsx(RunInfoHover, {
35407
35611
  runInfo: response.runInfo
35408
35612
  }), messageComponent, (response == null ? void 0 : response.result) && /* @__PURE__ */ jsx(RenderedResult, {
35409
- result: response.result
35613
+ result: response.result,
35614
+ source
35410
35615
  })]
35411
35616
  });
35412
35617
  }
@@ -35449,14 +35654,30 @@ function Banners({
35449
35654
  });
35450
35655
  }
35451
35656
  function RenderedResult({
35452
- result
35657
+ result,
35658
+ source
35453
35659
  }) {
35454
35660
  const [renderer, setRenderer] = useState();
35661
+ const {
35662
+ setQuery
35663
+ } = useContext(QueryEditorContext);
35455
35664
  useEffect(() => {
35456
35665
  const renderer2 = document.createElement("malloy-render");
35457
35666
  renderer2.malloyResult = result;
35667
+ renderer2.onDrill = ({
35668
+ stableQuery
35669
+ }) => {
35670
+ const rootQuery = new ASTQuery({
35671
+ query: stableQuery,
35672
+ source
35673
+ });
35674
+ setQuery == null ? void 0 : setQuery(rootQuery.build());
35675
+ };
35676
+ renderer2.tableConfig = {
35677
+ enableDrill: true
35678
+ };
35458
35679
  setRenderer(renderer2);
35459
- }, [result]);
35680
+ }, [result, source, setQuery]);
35460
35681
  if (renderer) {
35461
35682
  return /* @__PURE__ */ jsx(DOMElement, {
35462
35683
  element: renderer,
@@ -35696,6 +35917,7 @@ function ResultPanel({
35696
35917
  })]
35697
35918
  })]
35698
35919
  }), /* @__PURE__ */ jsx(ResultDisplay, {
35920
+ source,
35699
35921
  query: submittedQuery
35700
35922
  })]
35701
35923
  })
@@ -36042,10 +36264,12 @@ function FieldTokenWithActions({
36042
36264
  }) {
36043
36265
  const {
36044
36266
  rootQuery,
36045
- setQuery,
36046
- currentNestView
36267
+ setQuery
36047
36268
  } = React__default.useContext(QueryEditorContext);
36048
- const view = currentNestView ?? viewDef;
36269
+ const {
36270
+ focusedNestView
36271
+ } = useQueryFocus();
36272
+ const view = focusedNestView ?? viewDef;
36049
36273
  const {
36050
36274
  groupByDisabledReason,
36051
36275
  aggregateDisabledReason,
@@ -36146,7 +36370,13 @@ function FieldTokenWithActions({
36146
36370
  onTooltipOpenChange: setIsTooltipOpen
36147
36371
  })]
36148
36372
  }) : null,
36149
- onClick: field.kind === "dimension" && !groupByDisabledReason ? () => handleAddOperationAction("groupBy") : field.kind === "measure" && !aggregateDisabledReason ? () => handleAddOperationAction("aggregate") : field.kind === "view" ? () => handleAddView() : void 0,
36373
+ onClick: field.kind === "dimension" && !groupByDisabledReason ? () => handleAddOperationAction("groupBy") : field.kind === "measure" && !aggregateDisabledReason ? () => handleAddOperationAction("aggregate") : field.kind === "view" ? () => {
36374
+ if (rootQuery == null ? void 0 : rootQuery.isEmpty()) {
36375
+ handleSetView();
36376
+ } else {
36377
+ handleAddView();
36378
+ }
36379
+ } : void 0,
36150
36380
  hoverActionsVisible: isFilterPopoverOpen || isTooltipOpen,
36151
36381
  tooltip: /* @__PURE__ */ jsx(FieldHoverCard, {
36152
36382
  field,