flexdesk 0.15.2 → 0.16.1

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.
Files changed (195) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +70 -0
  3. package/THIRD_PARTY_LICENSES.md +0 -0
  4. package/css/base.css +0 -0
  5. package/css/flexdesk.css +153 -0
  6. package/css/overrides.css +0 -0
  7. package/css/reset.css +0 -0
  8. package/css/tokens.css +0 -0
  9. package/dist/canvas.js +0 -0
  10. package/dist/canvas.js.map +0 -0
  11. package/dist/charts.js +0 -0
  12. package/dist/charts.js.map +0 -0
  13. package/dist/chunk-3PHCPZHT.js +0 -0
  14. package/dist/chunk-3PHCPZHT.js.map +0 -0
  15. package/dist/chunk-6JSOVNID.js +0 -0
  16. package/dist/chunk-6JSOVNID.js.map +0 -0
  17. package/dist/chunk-B5Q53LAJ.js +111 -0
  18. package/dist/chunk-B5Q53LAJ.js.map +7 -0
  19. package/dist/chunk-FBGP5KN5.js +0 -0
  20. package/dist/chunk-FBGP5KN5.js.map +0 -0
  21. package/dist/chunk-FL5KFNQH.js +0 -0
  22. package/dist/chunk-FL5KFNQH.js.map +0 -0
  23. package/dist/chunk-FOOS3T5L.js +0 -0
  24. package/dist/chunk-FOOS3T5L.js.map +0 -0
  25. package/dist/chunk-JYWURG5T.js +0 -0
  26. package/dist/chunk-JYWURG5T.js.map +0 -0
  27. package/dist/chunk-NLTOY57M.js +0 -0
  28. package/dist/chunk-NLTOY57M.js.map +0 -0
  29. package/dist/chunk-NNFTETK3.js +0 -0
  30. package/dist/chunk-NNFTETK3.js.map +0 -0
  31. package/dist/chunk-O5OHMWBB.js +0 -0
  32. package/dist/chunk-O5OHMWBB.js.map +0 -0
  33. package/dist/chunk-RQM5Z2MU.js +0 -0
  34. package/dist/chunk-RQM5Z2MU.js.map +0 -0
  35. package/dist/chunk-SN4LSOAR.js +0 -0
  36. package/dist/chunk-SN4LSOAR.js.map +0 -0
  37. package/dist/{chunk-CWPMLX67.js → chunk-W6FBJLPW.js} +2 -110
  38. package/dist/{chunk-CWPMLX67.js.map → chunk-W6FBJLPW.js.map} +3 -3
  39. package/dist/chunk-WVFGV5FT.js +0 -0
  40. package/dist/chunk-WVFGV5FT.js.map +0 -0
  41. package/dist/core.js +0 -0
  42. package/dist/core.js.map +0 -0
  43. package/dist/editor.js +0 -0
  44. package/dist/editor.js.map +0 -0
  45. package/dist/flexdesk.css +153 -0
  46. package/dist/flow.js +0 -0
  47. package/dist/flow.js.map +0 -0
  48. package/dist/host.js +80 -1
  49. package/dist/host.js.map +3 -3
  50. package/dist/reset.css +0 -0
  51. package/dist/tile_registry-6WZPTUZV.js +0 -0
  52. package/dist/tile_registry-6WZPTUZV.js.map +0 -0
  53. package/dist/tiles.js +471 -16
  54. package/dist/tiles.js.map +4 -4
  55. package/dist/tokens.css +0 -0
  56. package/dist/widgets.js +73 -9
  57. package/dist/widgets.js.map +3 -3
  58. package/dist/wm.js +4 -2
  59. package/dist/wm.js.map +1 -1
  60. package/package.json +1 -1
  61. package/src/canvas/canvas_adapter.js +0 -0
  62. package/src/canvas/edge_router.js +0 -0
  63. package/src/canvas/graph/connector_router.js +0 -0
  64. package/src/canvas/graph/history_service.js +0 -0
  65. package/src/canvas/graph/node_platform.js +0 -0
  66. package/src/canvas/index.js +0 -0
  67. package/src/charts/chart_types.js +0 -0
  68. package/src/charts/data_series_plot_window.js +0 -0
  69. package/src/charts/downsample.js +0 -0
  70. package/src/charts/plot_config.js +0 -0
  71. package/src/charts/plot_popout_window.js +0 -0
  72. package/src/charts/plotly_wrapper.js +0 -0
  73. package/src/core/event_bus.js +0 -0
  74. package/src/core/logging.js +0 -0
  75. package/src/core/settings.js +0 -0
  76. package/src/core/state_guard.js +0 -0
  77. package/src/core/state_guard_config.js +0 -0
  78. package/src/core/state_machine.js +0 -0
  79. package/src/editor/editor_pane.js +0 -0
  80. package/src/editor/monaco_editor_factory.js +0 -0
  81. package/src/editor/monaco_loader.js +0 -0
  82. package/src/editor/notebook_search.js +0 -0
  83. package/src/editor/notebook_tab_bar.js +0 -0
  84. package/src/editor/notebook_undo_manager.js +0 -0
  85. package/src/editor/split_pane_container.js +0 -0
  86. package/src/flow/canvas_editor/editor.js +0 -0
  87. package/src/flow/canvas_editor/index.js +0 -0
  88. package/src/flow/canvas_editor/layout.js +0 -0
  89. package/src/flow/canvas_editor/strings.js +0 -0
  90. package/src/flow/kit/always_before.js +0 -0
  91. package/src/flow/kit/catalogue.js +0 -0
  92. package/src/flow/kit/chip_input.js +0 -0
  93. package/src/flow/kit/dom.js +0 -0
  94. package/src/flow/kit/findings.js +0 -0
  95. package/src/flow/kit/history.js +0 -0
  96. package/src/flow/kit/index.js +0 -0
  97. package/src/flow/kit/keys.js +0 -0
  98. package/src/flow/kit/logic_graph.js +0 -0
  99. package/src/flow/kit/references.js +0 -0
  100. package/src/flow/kit/settings_panel.js +0 -0
  101. package/src/flow/kit/settings_schema.js +0 -0
  102. package/src/flow/kit/step_picker.js +0 -0
  103. package/src/flow/kit/strings.js +0 -0
  104. package/src/flow/kit/value_picker.js +0 -0
  105. package/src/flow/kit/widgets.js +0 -0
  106. package/src/flow/lanes/editor.js +0 -0
  107. package/src/flow/lanes/index.js +0 -0
  108. package/src/flow/lanes/layout.js +0 -0
  109. package/src/flow/lanes/pipeline.js +0 -0
  110. package/src/flow/lanes/preview.js +0 -0
  111. package/src/flow/lanes/strings.js +0 -0
  112. package/src/flow/outline/editor.js +0 -0
  113. package/src/flow/outline/index.js +0 -0
  114. package/src/flow/outline/mapping.js +0 -0
  115. package/src/flow/outline/menu.js +0 -0
  116. package/src/flow/outline/operations.js +0 -0
  117. package/src/flow/outline/recognise.js +0 -0
  118. package/src/flow/outline/strings.js +0 -0
  119. package/src/help/help_modal.js +0 -0
  120. package/src/help/help_registry.js +0 -0
  121. package/src/host/external_links.js +94 -0
  122. package/src/host/host.js +4 -1
  123. package/src/host/photino_host.js +7 -0
  124. package/src/host/photino_host.md +18 -0
  125. package/src/host/pywebview_host.js +0 -0
  126. package/src/tiles/chart_tiles.js +0 -0
  127. package/src/tiles/config_schema.js +0 -0
  128. package/src/tiles/layout_persistence.js +0 -0
  129. package/src/tiles/list_tiles.js +422 -0
  130. package/src/tiles/scope_picker.js +96 -0
  131. package/src/tiles/tile_base.js +11 -0
  132. package/src/tiles/tile_grid.js +36 -1
  133. package/src/tiles/tile_registry.js +0 -0
  134. package/src/tiling/bottom_panel_tabs.js +0 -0
  135. package/src/tiling/command_palette.js +0 -0
  136. package/src/tiling/content_registry.js +0 -0
  137. package/src/tiling/desktops.js +0 -0
  138. package/src/tiling/entity_sources.js +0 -0
  139. package/src/tiling/keymap.js +0 -0
  140. package/src/tiling/kind_taxonomy.js +0 -0
  141. package/src/tiling/landing_table.js +0 -0
  142. package/src/tiling/loading_overlay.js +0 -0
  143. package/src/tiling/nav_panel.js +0 -0
  144. package/src/tiling/page_factory.js +0 -0
  145. package/src/tiling/panel_keys.js +0 -0
  146. package/src/tiling/shell.js +0 -0
  147. package/src/tiling/tab_strip.js +0 -0
  148. package/src/tiling/tile_breadcrumb.js +0 -0
  149. package/src/tiling/tile_renderer.js +0 -0
  150. package/src/tiling/tile_tab_menu.js +0 -0
  151. package/src/tiling/tile_tree.js +0 -0
  152. package/src/tiling/wm.js +0 -0
  153. package/src/tiling/zoom.js +0 -0
  154. package/src/ui/base/component_base.js +0 -0
  155. package/src/ui/base/controller_base.js +0 -0
  156. package/src/ui/base/page_base.js +0 -0
  157. package/src/ui/components/about_dialog.js +0 -0
  158. package/src/ui/components/action_dropdown.js +0 -0
  159. package/src/ui/components/alert_log.js +0 -0
  160. package/src/ui/components/alerts_pane.js +0 -0
  161. package/src/ui/components/attribute_list_editor.js +0 -0
  162. package/src/ui/components/autocomplete_field.js +0 -0
  163. package/src/ui/components/column_fit.js +0 -0
  164. package/src/ui/components/computing_status_window.js +0 -0
  165. package/src/ui/components/confirm_dialog.js +0 -0
  166. package/src/ui/components/context_menu.js +0 -0
  167. package/src/ui/components/data_table.js +0 -0
  168. package/src/ui/components/detail_header.js +0 -0
  169. package/src/ui/components/drag_reorder.js +0 -0
  170. package/src/ui/components/fact_table.js +0 -0
  171. package/src/ui/components/gallery_picker.js +0 -0
  172. package/src/ui/components/inline_renamer.js +0 -0
  173. package/src/ui/components/managed_window.js +0 -0
  174. package/src/ui/components/modal.js +0 -0
  175. package/src/ui/components/notification_history.js +0 -0
  176. package/src/ui/components/record_list.js +0 -0
  177. package/src/ui/components/row_activation.js +0 -0
  178. package/src/ui/components/slide_out_panel.js +0 -0
  179. package/src/ui/components/slider_field.js +0 -0
  180. package/src/ui/components/sortable_list.js +26 -4
  181. package/src/ui/components/table_filter_chips.js +0 -0
  182. package/src/ui/components/table_state_store.js +0 -0
  183. package/src/ui/components/table_values.js +0 -0
  184. package/src/ui/components/toast.js +0 -0
  185. package/src/ui/components/tree_view.js +0 -0
  186. package/src/ui/components/window_taskbar.js +0 -0
  187. package/src/ui/controllers/panel_state_machine.js +0 -0
  188. package/src/ui/controllers/window_chrome_controller.js +0 -0
  189. package/src/ui/notification_center.js +0 -0
  190. package/src/ui/utils/auto_scrollbars.js +0 -0
  191. package/src/ui/utils/native_context_menu.js +61 -0
  192. package/src/ui/utils/overlay_scrollbar.js +0 -0
  193. package/src/ui/utils/raf_resize_observer.js +0 -0
  194. package/src/ui/utils/scroll_lock.js +0 -0
  195. package/src/ui/utils/tooltip_service.js +0 -0
package/dist/tiles.js CHANGED
@@ -14,6 +14,10 @@ import {
14
14
  import {
15
15
  ActionDropdown
16
16
  } from "./chunk-O5OHMWBB.js";
17
+ import {
18
+ showContextMenu
19
+ } from "./chunk-B5Q53LAJ.js";
20
+ import "./chunk-NNFTETK3.js";
17
21
  import {
18
22
  buildHistogramTrace,
19
23
  buildLegendLayout,
@@ -234,6 +238,16 @@ var TileBase = class {
234
238
  this._lastLoadedAt = null;
235
239
  this._loadSeq = 0;
236
240
  this.comparisonData = null;
241
+ this.scope = null;
242
+ }
243
+ /**
244
+ * Called when the board's scope changes (`TileGrid.setScope`), after
245
+ * `this.scope` is set. A no-op here: a tile whose rows the grid's data
246
+ * source loads is reloaded by the grid, and one fed through `setData` is
247
+ * fed again by whoever owns its data.
248
+ * @param {*} _scope
249
+ */
250
+ onScopeChange(_scope) {
237
251
  }
238
252
  /**
239
253
  * Set comparison data for overlay rendering.
@@ -696,7 +710,7 @@ var TileBase = class {
696
710
  * @private
697
711
  */
698
712
  _onConfigClick() {
699
- this.element?.closest(".tile-grid")?.querySelectorAll(".tile.tile--selected").forEach((el) => el.classList.remove("tile--selected"));
713
+ this.element?.closest(".tile-grid")?.querySelectorAll(".tile.tile--selected").forEach((el2) => el2.classList.remove("tile--selected"));
700
714
  this.element?.classList.add("tile--selected");
701
715
  this.eventBus?.emit?.("tile:config-request", {
702
716
  tileId: this.id,
@@ -1133,10 +1147,10 @@ var ChartTile = class extends TileBase {
1133
1147
  * not awaited: a rejected purge (Plotly absent, already purged) must not
1134
1148
  * surface as an unhandled rejection. */
1135
1149
  _releasePlot() {
1136
- const el = this._plotEl;
1150
+ const el2 = this._plotEl;
1137
1151
  this._plotEl = null;
1138
1152
  this._drawn = false;
1139
- if (el) purge(el).catch(() => {
1153
+ if (el2) purge(el2).catch(() => {
1140
1154
  });
1141
1155
  }
1142
1156
  // ── the draw ────────────────────────────────────────────────────────
@@ -1487,11 +1501,11 @@ var KpiTile = class extends TileBase {
1487
1501
  if (delta) box.append(delta);
1488
1502
  const foot = [this.config.foot, d.note].filter(Boolean).join(" \xB7 ");
1489
1503
  if (foot) {
1490
- const el = document.createElement("div");
1491
- el.className = "fd-kpi__foot";
1492
- el.textContent = foot;
1493
- el.title = foot;
1494
- box.append(el);
1504
+ const el2 = document.createElement("div");
1505
+ el2.className = "fd-kpi__foot";
1506
+ el2.textContent = foot;
1507
+ el2.title = foot;
1508
+ box.append(el2);
1495
1509
  }
1496
1510
  content.append(box);
1497
1511
  }
@@ -1503,19 +1517,19 @@ var KpiTile = class extends TileBase {
1503
1517
  const reference = Number(rawReference);
1504
1518
  if (!Number.isFinite(value) || !Number.isFinite(reference)) return null;
1505
1519
  const change = value - reference;
1506
- const el = document.createElement("div");
1507
- el.className = "fd-kpi__delta";
1508
- if (change > 0) el.classList.add(this.config.invert_delta ? "fd-kpi__delta--down" : "fd-kpi__delta--up");
1509
- else if (change < 0) el.classList.add(this.config.invert_delta ? "fd-kpi__delta--up" : "fd-kpi__delta--down");
1520
+ const el2 = document.createElement("div");
1521
+ el2.className = "fd-kpi__delta";
1522
+ if (change > 0) el2.classList.add(this.config.invert_delta ? "fd-kpi__delta--down" : "fd-kpi__delta--up");
1523
+ else if (change < 0) el2.classList.add(this.config.invert_delta ? "fd-kpi__delta--up" : "fd-kpi__delta--down");
1510
1524
  const arrow = change > 0 ? "\u25B2" : change < 0 ? "\u25BC" : "\u2013";
1511
1525
  const magnitude = this.config.delta_relative ? reference === 0 ? null : formatValue(Math.abs(change / reference), {
1512
1526
  format: "percent",
1513
1527
  decimals: this.config.decimals ?? 1
1514
1528
  }) : this._format(Math.abs(change), false);
1515
1529
  const sign = change > 0 ? "+" : change < 0 ? "-" : "";
1516
- el.textContent = magnitude === null ? `${arrow} \u2014` : `${arrow} ${sign}${magnitude}`;
1517
- el.title = `Compared with ${this._format(reference, false)}`;
1518
- return el;
1530
+ el2.textContent = magnitude === null ? `${arrow} \u2014` : `${arrow} ${sign}${magnitude}`;
1531
+ el2.title = `Compared with ${this._format(reference, false)}`;
1532
+ return el2;
1519
1533
  }
1520
1534
  _format(value, withAffixes) {
1521
1535
  const format = this.config.format || "number";
@@ -2139,6 +2153,399 @@ function getLayoutPersistence() {
2139
2153
  return instance;
2140
2154
  }
2141
2155
 
2156
+ // src/tiles/list_tiles.js
2157
+ init_tile_registry();
2158
+ var TONES = /* @__PURE__ */ new Set(["danger", "warning", "ok", "muted"]);
2159
+ function normalize(data, key) {
2160
+ if (data == null) return null;
2161
+ const d = Array.isArray(data) ? { [key]: data } : data;
2162
+ if (typeof d !== "object" || !Array.isArray(d[key])) return null;
2163
+ const items = d[key].filter((r) => r && typeof r === "object" && r.key != null);
2164
+ const total = Number.isFinite(Number(d.total)) && d.total !== null && d.total !== "" ? Number(d.total) : items.length;
2165
+ return {
2166
+ items,
2167
+ total: Math.max(total, items.length),
2168
+ note: typeof d.note === "string" ? d.note : "",
2169
+ empty: typeof d.empty === "string" ? d.empty : "",
2170
+ action: d.action && typeof d.action === "object" && d.action.label ? d.action : null
2171
+ };
2172
+ }
2173
+ var normalizeListData = (data) => normalize(data, "rows");
2174
+ var normalizePeopleData = (data) => normalize(data, "people");
2175
+ function el(tag, cls, text) {
2176
+ const e = document.createElement(tag);
2177
+ if (cls) e.className = cls;
2178
+ if (text != null) e.textContent = String(text);
2179
+ return e;
2180
+ }
2181
+ function glyph(name, cls = "") {
2182
+ const g = el("span", `material-symbols-outlined${cls ? ` ${cls}` : ""}`, name);
2183
+ g.setAttribute("aria-hidden", "true");
2184
+ return g;
2185
+ }
2186
+ var toneClass = (base, tone) => TONES.has(tone) ? ` ${base}--${tone}` : "";
2187
+ var RowsTile = class extends TileBase {
2188
+ static _KEY = "rows";
2189
+ constructor(options = {}) {
2190
+ super(options);
2191
+ this.onRowActivate = typeof options.onRowActivate === "function" ? options.onRowActivate : null;
2192
+ this.rowMenu = typeof options.rowMenu === "function" ? options.rowMenu : null;
2193
+ this.onRowMenuAction = typeof options.onRowMenuAction === "function" ? options.onRowMenuAction : null;
2194
+ this.onAction = typeof options.onAction === "function" ? options.onAction : null;
2195
+ this.rowsDraggable = options.rowsDraggable === true;
2196
+ this._items = /* @__PURE__ */ new Map();
2197
+ this._onClick = (e) => this._click(e);
2198
+ this._onKey = (e) => this._key(e);
2199
+ this._onMenu = (e) => this._menu(e);
2200
+ }
2201
+ /** Pure and static-only — it runs inside `TileBase`'s constructor. */
2202
+ getDefaultConfig() {
2203
+ return { title: "", empty_text: "", max_rows: 0 };
2204
+ }
2205
+ getConfigSchema() {
2206
+ return {
2207
+ fields: [
2208
+ { name: "title", type: "text", label: "Tile title" },
2209
+ {
2210
+ name: "empty_text",
2211
+ type: "text",
2212
+ label: "When there is nothing",
2213
+ help: 'What the tile says when the list is empty, e.g. "Nothing is late."'
2214
+ },
2215
+ {
2216
+ name: "max_rows",
2217
+ type: "number",
2218
+ label: "Rows at most",
2219
+ min: 0,
2220
+ help: "0 shows them all; the tile scrolls."
2221
+ }
2222
+ ]
2223
+ };
2224
+ }
2225
+ mount(container) {
2226
+ super.mount(container);
2227
+ applyTitle2(this);
2228
+ const c = this.contentElement;
2229
+ if (!c) return;
2230
+ c.addEventListener("click", this._onClick);
2231
+ c.addEventListener("keydown", this._onKey);
2232
+ c.addEventListener("contextmenu", this._onMenu);
2233
+ }
2234
+ /** `null` is "the grid has no data" — see `ChartTile.update`. */
2235
+ update(data, config) {
2236
+ if (this._disposed) return;
2237
+ super.update(data === null ? void 0 : data, config);
2238
+ if (config) applyTitle2(this);
2239
+ }
2240
+ /** Hand this tile its rows; null is the empty state. */
2241
+ setData(data) {
2242
+ if (this._disposed) return;
2243
+ this.data = data ?? null;
2244
+ if (this.contentElement) this.render(this.data);
2245
+ }
2246
+ /** The item drawn under `key`, or null. */
2247
+ itemFor(key) {
2248
+ return this._items.get(String(key)) ?? null;
2249
+ }
2250
+ /** The element of the item drawn under `key`, or null. */
2251
+ rowElement(key) {
2252
+ const want = String(key);
2253
+ return [...this.contentElement?.querySelectorAll("[data-row-key]") || []].find((r) => r.dataset.rowKey === want) || null;
2254
+ }
2255
+ render(data) {
2256
+ const content = this.contentElement;
2257
+ if (!content) return;
2258
+ const norm = normalize(data, this.constructor._KEY);
2259
+ this._items.clear();
2260
+ if (!norm) {
2261
+ this.showEmpty("Nothing to show.");
2262
+ return;
2263
+ }
2264
+ const max = Math.max(0, Math.floor(Number(this.config.max_rows) || 0));
2265
+ const shown = max ? norm.items.slice(0, max) : norm.items;
2266
+ const box = el("div", `fd-list-tile fd-list-tile--${this.constructor._KEY}`);
2267
+ const bar = el("div", "fd-list-tile__bar");
2268
+ const count = el(
2269
+ "span",
2270
+ "fd-list-tile__count",
2271
+ shown.length < norm.total ? `${shown.length} of ${norm.total}` : String(norm.total)
2272
+ );
2273
+ count.title = `${norm.total} in all`;
2274
+ bar.append(count, el("span", "fd-list-tile__spacer"));
2275
+ if (norm.action) {
2276
+ const b = el("button", "fd-list-tile__action");
2277
+ b.type = "button";
2278
+ b.dataset.listAction = "action";
2279
+ if (norm.action.icon) b.append(glyph(norm.action.icon));
2280
+ b.append(el("span", null, norm.action.label));
2281
+ bar.append(b);
2282
+ }
2283
+ box.append(bar);
2284
+ const list = el("div", "fd-list-tile__rows");
2285
+ list.setAttribute("role", "list");
2286
+ if (!shown.length) {
2287
+ list.append(el("div", "fd-list-tile__empty", norm.empty || this.config.empty_text || "Nothing here."));
2288
+ }
2289
+ for (const item of shown) {
2290
+ const key = String(item.key);
2291
+ if (this._items.has(key)) continue;
2292
+ this._items.set(key, item);
2293
+ const row = this._renderItem(item);
2294
+ row.classList.add("fd-list-tile__row");
2295
+ row.dataset.rowKey = key;
2296
+ row.setAttribute("role", "button");
2297
+ row.tabIndex = 0;
2298
+ if (this.rowsDraggable) row.draggable = true;
2299
+ const wrap = el("div", "fd-list-tile__item");
2300
+ wrap.setAttribute("role", "listitem");
2301
+ wrap.append(row);
2302
+ list.append(wrap);
2303
+ }
2304
+ box.append(list);
2305
+ if (norm.note) {
2306
+ const note = el("div", "fd-list-tile__note", norm.note);
2307
+ note.setAttribute("role", "note");
2308
+ note.title = norm.note;
2309
+ box.append(note);
2310
+ }
2311
+ content.replaceChildren(box);
2312
+ }
2313
+ /** One item's row element. @abstract */
2314
+ _renderItem(_item) {
2315
+ return el("div");
2316
+ }
2317
+ // ── input ───────────────────────────────────────────────────────────
2318
+ _rowOf(target) {
2319
+ const row = target?.closest?.("[data-row-key]");
2320
+ return row && this.contentElement?.contains(row) ? row : null;
2321
+ }
2322
+ _click(e) {
2323
+ if (e.target?.closest?.("[data-list-action]")) {
2324
+ this._call(this.onAction, { tile: this, event: e });
2325
+ return;
2326
+ }
2327
+ const row = this._rowOf(e.target);
2328
+ if (row) this._activate(row, e);
2329
+ }
2330
+ _activate(rowEl, event) {
2331
+ const row = this.itemFor(rowEl.dataset.rowKey);
2332
+ if (row) this._call(this.onRowActivate, { row, event, tile: this });
2333
+ }
2334
+ _key(e) {
2335
+ const rowEl = this._rowOf(e.target);
2336
+ if (!rowEl) return;
2337
+ if (e.key === "ContextMenu" || e.key === "F10" && e.shiftKey) {
2338
+ const r = rowEl.getBoundingClientRect();
2339
+ if (this._openMenu(rowEl, e, r.left + 16, r.bottom)) e.preventDefault();
2340
+ return;
2341
+ }
2342
+ if ((e.key === "Enter" || e.key === " ") && !e.altKey) {
2343
+ e.preventDefault();
2344
+ this._activate(rowEl, e);
2345
+ return;
2346
+ }
2347
+ const rows = [...this.contentElement.querySelectorAll("[data-row-key]")];
2348
+ const i = rows.indexOf(rowEl);
2349
+ const to = { ArrowDown: i + 1, ArrowUp: i - 1, Home: 0, End: rows.length - 1 }[e.key];
2350
+ if (to === void 0 || !rows[to]) return;
2351
+ e.preventDefault();
2352
+ rows[to].focus();
2353
+ }
2354
+ _menu(e) {
2355
+ const rowEl = this._rowOf(e.target);
2356
+ if (rowEl && this._openMenu(rowEl, e, e.clientX, e.clientY)) e.preventDefault();
2357
+ }
2358
+ /** Show the row's menu, if it has one. → whether it did. */
2359
+ _openMenu(rowEl, event, x, y) {
2360
+ const row = this.itemFor(rowEl.dataset.rowKey);
2361
+ if (!row || typeof this.rowMenu !== "function") return false;
2362
+ let items = null;
2363
+ try {
2364
+ items = this.rowMenu({ row, event, tile: this });
2365
+ } catch (err) {
2366
+ console.error(`[${this.constructor.name}] rowMenu threw:`, err);
2367
+ return false;
2368
+ }
2369
+ if (!Array.isArray(items) || !items.length) return false;
2370
+ showContextMenu(x, y, items, (action) => this._call(this.onRowMenuAction, { action, row, tile: this }));
2371
+ return true;
2372
+ }
2373
+ /** A hook, called so that one that throws is reported and the tile goes on. */
2374
+ _call(fn, arg) {
2375
+ if (typeof fn !== "function") return;
2376
+ try {
2377
+ fn(arg);
2378
+ } catch (err) {
2379
+ console.error(`[${this.constructor.name}] a callback threw:`, err);
2380
+ }
2381
+ }
2382
+ dispose() {
2383
+ const c = this.contentElement;
2384
+ if (c) {
2385
+ c.removeEventListener("click", this._onClick);
2386
+ c.removeEventListener("keydown", this._onKey);
2387
+ c.removeEventListener("contextmenu", this._onMenu);
2388
+ }
2389
+ this._items.clear();
2390
+ super.dispose();
2391
+ }
2392
+ };
2393
+ var ListTile = class extends RowsTile {
2394
+ static TYPE = "flexdesk.list";
2395
+ static TITLE = "List";
2396
+ static ICON = "list";
2397
+ static DESCRIPTION = "Rows you supply, with a count, a click and a right-click menu per row.";
2398
+ static DEFAULT_SIZE = { w: 6, h: 4 };
2399
+ static SIZE_CONSTRAINTS = { minW: 3, minH: 2, maxW: 12, maxH: 12 };
2400
+ static _KEY = "rows";
2401
+ _renderItem(r) {
2402
+ const row = el("div", r.className ? String(r.className) : null);
2403
+ if (r.icon) {
2404
+ const g = glyph(r.icon, "fd-list-tile__icon");
2405
+ if (r.iconTitle) {
2406
+ g.title = r.iconTitle;
2407
+ g.removeAttribute("aria-hidden");
2408
+ }
2409
+ row.append(g);
2410
+ }
2411
+ if (r.lead != null && r.lead !== "") row.append(el("span", "fd-list-tile__lead", r.lead));
2412
+ const title = el("span", "fd-list-tile__title", r.title ?? "");
2413
+ title.title = r.tip || String(r.title ?? "");
2414
+ row.append(title);
2415
+ for (const c of Array.isArray(r.cells) ? r.cells : []) {
2416
+ if (!c) continue;
2417
+ const cell = el("span", `fd-list-tile__cell${toneClass("fd-list-tile__cell", c.tone)}${c.className ? ` ${c.className}` : ""}`);
2418
+ if (c.icon) cell.append(glyph(c.icon));
2419
+ if (c.text != null && c.text !== "") cell.append(document.createTextNode(String(c.text)));
2420
+ if (c.title) cell.title = c.title;
2421
+ row.append(cell);
2422
+ }
2423
+ return row;
2424
+ }
2425
+ };
2426
+ var PeopleTile = class extends RowsTile {
2427
+ static TYPE = "flexdesk.people";
2428
+ static TITLE = "People";
2429
+ static ICON = "groups";
2430
+ static DESCRIPTION = "One row per person, with how much is on them.";
2431
+ static DEFAULT_SIZE = { w: 6, h: 4 };
2432
+ static SIZE_CONSTRAINTS = { minW: 3, minH: 2, maxW: 12, maxH: 12 };
2433
+ static _KEY = "people";
2434
+ getDefaultConfig() {
2435
+ return { ...super.getDefaultConfig(), count_label: "open" };
2436
+ }
2437
+ getConfigSchema() {
2438
+ const { fields } = super.getConfigSchema();
2439
+ return { fields: [...fields, {
2440
+ name: "count_label",
2441
+ type: "text",
2442
+ label: "Count reads",
2443
+ help: `After each person's number, e.g. "open" for "3 open".`
2444
+ }] };
2445
+ }
2446
+ _renderItem(p) {
2447
+ const row = el("div", "fd-people-tile__row");
2448
+ row.append(glyph(p.icon || (p.muted ? "person_off" : "person"), "fd-list-tile__icon"));
2449
+ row.append(el("span", `fd-people-tile__name${p.muted ? " fd-people-tile__name--muted" : ""}`, p.name ?? p.key));
2450
+ const pills = el("span", "fd-people-tile__pills");
2451
+ for (const pill of Array.isArray(p.pills) ? p.pills : []) {
2452
+ if (!pill?.text) continue;
2453
+ const s = el("span", `fd-people-tile__pill${toneClass("fd-people-tile__pill", pill.tone)}`, pill.text);
2454
+ if (pill.title) s.title = pill.title;
2455
+ pills.append(s);
2456
+ }
2457
+ row.append(pills);
2458
+ const label = p.countLabel ?? this.config.count_label ?? "";
2459
+ row.append(el("span", "fd-people-tile__count", `${Number(p.count) || 0}${label ? ` ${label}` : ""}`));
2460
+ if (p.title) row.title = p.title;
2461
+ return row;
2462
+ }
2463
+ };
2464
+ function groupByPerson(items, personOf) {
2465
+ const by = /* @__PURE__ */ new Map();
2466
+ for (const item of Array.isArray(items) ? items : []) {
2467
+ const who = String(personOf(item) || "");
2468
+ if (!by.has(who)) by.set(who, { key: who, items: [] });
2469
+ by.get(who).items.push(item);
2470
+ }
2471
+ return [...by.values()].sort((a, b) => b.items.length - a.items.length || (a.key === "") - (b.key === "") || a.key.localeCompare(b.key));
2472
+ }
2473
+ function registerListTiles() {
2474
+ for (const Tile of [ListTile, PeopleTile]) {
2475
+ if (getWidget(Tile.TYPE) !== Tile) registerWidget(Tile);
2476
+ }
2477
+ }
2478
+ function applyTitle2(tile) {
2479
+ tile.setTitle(tile.config.title || tile.constructor.TITLE);
2480
+ }
2481
+
2482
+ // src/tiles/scope_picker.js
2483
+ function createScopePicker({ options = [], value, label = "Scope", onChange } = {}) {
2484
+ const opts = options.filter((o) => o && o.value != null).map((o) => ({ ...o, value: String(o.value) }));
2485
+ const known = (v) => opts.some((o) => o.value === String(v));
2486
+ let current = known(value) ? String(value) : opts[0]?.value ?? "";
2487
+ const element = document.createElement("span");
2488
+ element.className = "fd-scope-picker";
2489
+ element.setAttribute("role", "group");
2490
+ element.setAttribute("aria-label", label);
2491
+ const buttons = opts.map((o) => {
2492
+ const b = document.createElement("button");
2493
+ b.type = "button";
2494
+ b.className = "fd-scope-picker__btn";
2495
+ b.dataset.scope = o.value;
2496
+ b.textContent = o.label ?? o.value;
2497
+ if (o.hint) b.title = o.hint;
2498
+ element.append(b);
2499
+ return b;
2500
+ });
2501
+ const paint = () => {
2502
+ for (const b of buttons) {
2503
+ const on = b.dataset.scope === current;
2504
+ b.classList.toggle("fd-scope-picker__btn--on", on);
2505
+ b.setAttribute("aria-pressed", String(on));
2506
+ }
2507
+ };
2508
+ paint();
2509
+ const onClick = (e) => {
2510
+ const b = e.target?.closest?.("[data-scope]");
2511
+ if (!b || !element.contains(b) || b.dataset.scope === current) return;
2512
+ current = b.dataset.scope;
2513
+ paint();
2514
+ try {
2515
+ onChange?.(current);
2516
+ } catch (err) {
2517
+ console.error("[ScopePicker] onChange threw:", err);
2518
+ }
2519
+ };
2520
+ const onKey = (e) => {
2521
+ const i = buttons.indexOf(e.target);
2522
+ if (i < 0) return;
2523
+ const to = { ArrowRight: i + 1, ArrowLeft: i - 1, Home: 0, End: buttons.length - 1 }[e.key];
2524
+ if (to === void 0 || !buttons[to]) return;
2525
+ e.preventDefault();
2526
+ buttons[to].focus();
2527
+ };
2528
+ element.addEventListener("click", onClick);
2529
+ element.addEventListener("keydown", onKey);
2530
+ return {
2531
+ element,
2532
+ get value() {
2533
+ return current;
2534
+ },
2535
+ /** Press `v` without calling `onChange` (a value restored from elsewhere). */
2536
+ setValue(v) {
2537
+ if (!known(v)) return;
2538
+ current = String(v);
2539
+ paint();
2540
+ },
2541
+ dispose() {
2542
+ element.removeEventListener("click", onClick);
2543
+ element.removeEventListener("keydown", onKey);
2544
+ element.remove();
2545
+ }
2546
+ };
2547
+ }
2548
+
2142
2549
  // src/tiles/tile_grid.js
2143
2550
  init_tile_registry();
2144
2551
  var TileGrid = class {
@@ -2157,6 +2564,9 @@ var TileGrid = class {
2157
2564
  * @param {{resolve: Function}} [options.dataSource] - Per-tile data provider,
2158
2565
  * handed to every tile. With none, the grid is the broadcast-only grid
2159
2566
  * it has always been and no tile is ever asked to load anything.
2567
+ * @param {*} [options.scope=null] - The board's scope, a filter every tile
2568
+ * follows (0.16.1); see `setScope`.
2569
+ * @param {Function} [options.onScopeChange] - `(scope)` after `setScope` changed it.
2160
2570
  */
2161
2571
  constructor({
2162
2572
  container,
@@ -2179,9 +2589,13 @@ var TileGrid = class {
2179
2589
  stateGuard = null,
2180
2590
  // Injected, never constructed here: the grid does not know what
2181
2591
  // a binding is, only that a tile may have somewhere to ask.
2182
- dataSource = null
2592
+ dataSource = null,
2593
+ scope = null,
2594
+ onScopeChange = null
2183
2595
  }) {
2184
2596
  this.host = host;
2597
+ this.scope = scope;
2598
+ this._onScopeChangeCallback = onScopeChange;
2185
2599
  this.stateGuard = stateGuard;
2186
2600
  this.dataSource = dataSource;
2187
2601
  this.container = container;
@@ -2304,6 +2718,7 @@ var TileGrid = class {
2304
2718
  dataSource: this.dataSource
2305
2719
  });
2306
2720
  if (!tile) return null;
2721
+ tile.scope = this.scope;
2307
2722
  const layoutEntry = {
2308
2723
  id: tileId,
2309
2724
  type,
@@ -2374,6 +2789,39 @@ var TileGrid = class {
2374
2789
  tile.loadData?.({ force: true });
2375
2790
  });
2376
2791
  }
2792
+ /**
2793
+ * THE BOARD'S SCOPE: one filter every tile follows ("Everyone", "Assigned
2794
+ * by me", …), set from a `createScopePicker` or from anything else.
2795
+ *
2796
+ * The grid only carries the value; what it means is the embedder's. Every
2797
+ * tile gets it as `tile.scope` and through `tile.onScopeChange(scope)`, the
2798
+ * grid's `onScopeChange(scope)` is called, and a grid with a data source
2799
+ * reloads its tiles, since their rows depend on it. The scope is not part
2800
+ * of the layout: it is how the board is being looked at, not what is on it.
2801
+ *
2802
+ * @param {*} scope - Compared by JSON: setting the same scope again does nothing.
2803
+ * @returns {boolean} whether it changed
2804
+ */
2805
+ setScope(scope) {
2806
+ if (JSON.stringify(scope ?? null) === JSON.stringify(this.scope ?? null)) return false;
2807
+ this.scope = scope ?? null;
2808
+ this.tiles.forEach((tile) => {
2809
+ tile.scope = this.scope;
2810
+ try {
2811
+ tile.onScopeChange?.(this.scope);
2812
+ } catch (err) {
2813
+ console.error("[TileGrid] a tile's onScopeChange threw:", err);
2814
+ }
2815
+ });
2816
+ try {
2817
+ this._onScopeChangeCallback?.(this.scope);
2818
+ } catch (err) {
2819
+ console.error("[TileGrid] onScopeChange threw:", err);
2820
+ }
2821
+ this.eventBus?.emit?.("grid:scope-changed", { scope: this.scope });
2822
+ if (this.dataSource) this.reloadAll();
2823
+ return true;
2824
+ }
2377
2825
  /**
2378
2826
  * Get current layout.
2379
2827
  * @returns {Array} Layout array
@@ -2898,10 +3346,13 @@ export {
2898
3346
  FieldTypes,
2899
3347
  KpiTile,
2900
3348
  LayoutPersistence,
3349
+ ListTile,
3350
+ PeopleTile,
2901
3351
  TileBase,
2902
3352
  TileGrid,
2903
3353
  TileRegistry,
2904
3354
  configureLayoutPersistence,
3355
+ createScopePicker,
2905
3356
  createWidget,
2906
3357
  formatVariableLabel,
2907
3358
  getAllWidgetTypes,
@@ -2912,10 +3363,14 @@ export {
2912
3363
  getWidget,
2913
3364
  getWidgetCatalog,
2914
3365
  getWidgetMetadata,
3366
+ groupByPerson,
2915
3367
  hasWidget,
2916
3368
  mergeWithDefaults,
2917
3369
  normalizeChartData,
3370
+ normalizeListData,
3371
+ normalizePeopleData,
2918
3372
  registerChartTiles,
3373
+ registerListTiles,
2919
3374
  registerWidget,
2920
3375
  setTileLogger,
2921
3376
  validateConfig,