@grafloria/element 0.4.27 → 0.4.29
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/package.json +1 -1
- package/src/index.d.ts +1 -1
- package/src/index.js +1 -1
- package/src/lib/dashboard-kit/dashboard.js +76 -12
- package/src/lib/dashboard-kit/grid-binder.d.ts +9 -1
- package/src/lib/dashboard-kit/grid-binder.js +196 -3
- package/src/lib/dashboard-kit/index.d.ts +1 -1
- package/src/lib/dashboard-kit/index.js +1 -1
- package/src/lib/dashboard-kit/split-binder.js +3 -0
- package/src/lib/dashboard-kit/styles.js +21 -0
- package/src/lib/dashboard-kit/tabs.d.ts +14 -1
- package/src/lib/dashboard-kit/tabs.js +51 -1
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -79,7 +79,7 @@ export type { JoinColumn, JoinEnd, MatchTier, JoinGuidanceApi, JoinGuidanceOptio
|
|
|
79
79
|
export type { ErColumn, ErEntitySpec, ErRelationshipSpec, ErCardinality, ErSide, ErDiagramOptions, UmlClassSpec, UmlRelationshipSpec, UmlRelationKind, UmlSide, UmlDiagramOptions, ErEntityDelta, UmlClassDelta, CardEditingHandle, } from './lib/diagram-kit/index.js';
|
|
80
80
|
export { erTable, umlClass, erTables, umlClasses, CardHandle, ErTable, ErField, ErColumnList, UmlClass, UmlMemberList, } from './lib/diagram-kit/index.js';
|
|
81
81
|
export type { HandleApi } from './lib/diagram-kit/index.js';
|
|
82
|
-
export { bindDashboardGrid, normalizeCaption, captionReserve, captionBandHeight, paintCaptionBand, CAPTION_HEIGHT, CAPTION_HEIGHT_SUBTITLE, CAPTION_HEIGHT_TIGHT, CAPTION_PASS_THROUGH, rowHeightFor, boardHeightFor, columnUnitFor, cellToRect, pointToCell, sizeToSpan, gridItemFromCell, cellFromGridItem, buildCommitCommands, ensureDashboardKitStyles, DASHBOARD_KIT_STYLE_ID, dashboard, defaultWidgetRenderer, renderKpiWidget, renderLineWidget, renderBarWidget, renderDonutWidget, renderFunnelWidget, renderTableWidget, BUILT_IN_WIDGET_KINDS, } from './lib/dashboard-kit/index.js';
|
|
82
|
+
export { bindDashboardGrid, paintTabStrip, tabStripReserve, tabStripKey, TAB_STRIP_HEIGHT, TAB_DRAG_THRESHOLD, normalizeCaption, captionReserve, captionBandHeight, paintCaptionBand, CAPTION_HEIGHT, CAPTION_HEIGHT_SUBTITLE, CAPTION_HEIGHT_TIGHT, CAPTION_PASS_THROUGH, rowHeightFor, boardHeightFor, columnUnitFor, cellToRect, pointToCell, sizeToSpan, gridItemFromCell, cellFromGridItem, buildCommitCommands, ensureDashboardKitStyles, DASHBOARD_KIT_STYLE_ID, dashboard, defaultWidgetRenderer, renderKpiWidget, renderLineWidget, renderBarWidget, renderDonutWidget, renderFunnelWidget, renderTableWidget, BUILT_IN_WIDGET_KINDS, } from './lib/dashboard-kit/index.js';
|
|
83
83
|
export type { DashboardGridApi, DashboardGridOptions, DashboardGridHandle, CellRect, WorldRect, DashboardGridGeometry, TileDelta, DashboardOptions, DashboardSpec, DashboardSnapshot, DragHandleOption, DragGripOptions, DashboardHandle, DashboardViewSpec, DashboardWidgetSpec, SectionCaption, SectionCaptionOptions, TabsOptions, TabPage, SectionCaptionAction, SectionCaptionFont, WidgetHandle, WidgetRenderer, KpiWidgetData, LineWidgetData, LineSeries, BarWidgetData, DonutWidgetData, FunnelWidgetData, TableWidgetData, } from './lib/dashboard-kit/index.js';
|
|
84
84
|
export { bindStencilPalette, bindShapeDataPanel, ensureStencilKitStyles } from './lib/stencil-kit/index.js';
|
|
85
85
|
export type { StencilPaletteApi, StencilPaletteOptions, StencilPaletteHandle, ShapeDataPanelApi, ShapeDataPanelOptions, ShapeDataPanelHandle, } from './lib/stencil-kit/index.js';
|
package/src/index.js
CHANGED
|
@@ -230,7 +230,7 @@ export { erTable, umlClass, erTables, umlClasses, CardHandle, ErTable, ErField,
|
|
|
230
230
|
// GridItemConfig. Element-local code, so the Phase-0 wholesale re-export
|
|
231
231
|
// below does NOT cover it — this curated block is its only door.
|
|
232
232
|
// ===========================================================================
|
|
233
|
-
export { bindDashboardGrid, normalizeCaption, captionReserve, captionBandHeight, paintCaptionBand, CAPTION_HEIGHT, CAPTION_HEIGHT_SUBTITLE, CAPTION_HEIGHT_TIGHT, CAPTION_PASS_THROUGH, rowHeightFor, boardHeightFor, columnUnitFor, cellToRect, pointToCell, sizeToSpan, gridItemFromCell, cellFromGridItem, buildCommitCommands, ensureDashboardKitStyles, DASHBOARD_KIT_STYLE_ID,
|
|
233
|
+
export { bindDashboardGrid, paintTabStrip, tabStripReserve, tabStripKey, TAB_STRIP_HEIGHT, TAB_DRAG_THRESHOLD, normalizeCaption, captionReserve, captionBandHeight, paintCaptionBand, CAPTION_HEIGHT, CAPTION_HEIGHT_SUBTITLE, CAPTION_HEIGHT_TIGHT, CAPTION_PASS_THROUGH, rowHeightFor, boardHeightFor, columnUnitFor, cellToRect, pointToCell, sizeToSpan, gridItemFromCell, cellFromGridItem, buildCommitCommands, ensureDashboardKitStyles, DASHBOARD_KIT_STYLE_ID,
|
|
234
234
|
// The DATA-FIRST authoring API — the erDiagram()/umlDiagram() equivalent.
|
|
235
235
|
dashboard,
|
|
236
236
|
// …and the built-in renderers behind `kind`, so a dashboard is useful with
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
*/
|
|
45
45
|
import { __awaiter, __rest } from "tslib";
|
|
46
46
|
import { BatchCommand, BringNodeToFrontCommand, Command, GroupModel, NodeModel, RemoveFromGroupCommand, RemoveGroupCommand, RemoveNodeCommand, SendNodeToBackCommand, } from '@grafloria/engine';
|
|
47
|
-
import { bindDashboardGrid, } from './grid-binder.js';
|
|
47
|
+
import { bindDashboardGrid, parentPeerOf, } from './grid-binder.js';
|
|
48
48
|
import { bindDashboardSplit, SPLIT_TREE_KEY } from './split-binder.js';
|
|
49
49
|
import { gridItemFromCell } from './grid-mapping.js';
|
|
50
50
|
import { ensureDashboardKitStyles } from './styles.js';
|
|
@@ -230,6 +230,29 @@ function buildWidgetNode(w, rowHeight) {
|
|
|
230
230
|
node.removePort(p.id);
|
|
231
231
|
return node;
|
|
232
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* EVERY CHILD OF A TAB CONTAINER IS A PAGE. A page is a container, so a plain
|
|
235
|
+
* widget written among them is wrapped in one carrying its title. Without this
|
|
236
|
+
* the tab system simply ignored it: it was never positioned and painted at the
|
|
237
|
+
* board's origin at its 100×60 placeholder size, silently losing part of the
|
|
238
|
+
* author's layout (measured on a container mixing a widget with two pages).
|
|
239
|
+
*/
|
|
240
|
+
function wrapTabPages(ws) {
|
|
241
|
+
for (const w of ws) {
|
|
242
|
+
if (!w.widgets)
|
|
243
|
+
continue;
|
|
244
|
+
if (w.layout === 'tabs') {
|
|
245
|
+
w.widgets = w.widgets.map((c) => {
|
|
246
|
+
var _a;
|
|
247
|
+
return c.widgets
|
|
248
|
+
? c
|
|
249
|
+
: Object.assign(Object.assign({ id: `${c.id}__page`, title: (_a = c.title) !== null && _a !== void 0 ? _a : c.id }, (w.columns !== undefined ? { columns: w.columns } : {})), { widgets: [Object.assign(Object.assign({}, c), { x: 0, y: 0 })] });
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
wrapTabPages(w.widgets);
|
|
253
|
+
}
|
|
254
|
+
return ws;
|
|
255
|
+
}
|
|
233
256
|
function cloneWidgets(ws) {
|
|
234
257
|
return ws.map((w) => (Object.assign(Object.assign({}, w), (w.widgets ? { widgets: cloneWidgets(w.widgets) } : {}))));
|
|
235
258
|
}
|
|
@@ -319,12 +342,44 @@ function assignCells(widgets, columns) {
|
|
|
319
342
|
const cssEscape = (v) => typeof CSS !== 'undefined' && CSS.escape ? CSS.escape(v) : v.replace(/"/g, '\\"');
|
|
320
343
|
export function attachTabsRuntime(ctx, model, container, handle) {
|
|
321
344
|
var _a, _b, _c;
|
|
345
|
+
// Pages come from LIVE membership, in the authored order, with anything that
|
|
346
|
+
// arrived later on the end. Reading the authored spec instead meant a page
|
|
347
|
+
// that LEFT the container kept its tab — a tab pointing at nothing, which is
|
|
348
|
+
// exactly the "the content is taken and the tab remains" report — and a page
|
|
349
|
+
// that arrived never got one.
|
|
322
350
|
const pagesOf = (id) => {
|
|
323
|
-
var _a;
|
|
324
|
-
const
|
|
325
|
-
|
|
351
|
+
var _a, _b, _c, _d;
|
|
352
|
+
const cg = (_a = ctx.boardGroups.get(id)) !== null && _a !== void 0 ? _a : model.getGroup(id);
|
|
353
|
+
const authored = ((_c = (_b = ctx.specById.get(id)) === null || _b === void 0 ? void 0 : _b.widgets) !== null && _c !== void 0 ? _c : []).map((c) => c.id);
|
|
354
|
+
const live = [...((_d = cg === null || cg === void 0 ? void 0 : cg.members) !== null && _d !== void 0 ? _d : [])].filter((m) => !!model.getGroup(m));
|
|
355
|
+
const rank = (pid) => {
|
|
356
|
+
// A plain child is wrapped as `<id>__page`, so the authored order is
|
|
357
|
+
// keyed on the id BEFORE the wrap as well as after it.
|
|
358
|
+
const i = authored.indexOf(pid);
|
|
359
|
+
const j = i < 0 ? authored.indexOf(pid.replace(/__page$/, '')) : i;
|
|
360
|
+
return j < 0 ? Number.MAX_SAFE_INTEGER : j;
|
|
361
|
+
};
|
|
362
|
+
live.sort((a, b) => rank(a) - rank(b));
|
|
363
|
+
return live.map((pid) => { var _a, _b; return ({ id: pid, label: (_b = (_a = ctx.specById.get(pid)) === null || _a === void 0 ? void 0 : _a.title) !== null && _b !== void 0 ? _b : pid }); });
|
|
326
364
|
};
|
|
327
365
|
const isTabs = (id) => { var _a, _b; return ((_a = ctx.layoutOf.get(id)) !== null && _a !== void 0 ? _a : (_b = ctx.specById.get(id)) === null || _b === void 0 ? void 0 : _b.layout) === 'tabs' && pagesOf(id).length > 0; };
|
|
366
|
+
/**
|
|
367
|
+
* A tab dragged off its strip tears its page out onto the board that owns
|
|
368
|
+
* the container — VS Code's "drag a tab out and it becomes a group of its
|
|
369
|
+
* own". Only the board below can place it, so the gesture is handed to that
|
|
370
|
+
* binder; a container with a single page keeps it, because a tab container
|
|
371
|
+
* with no pages has nothing to show.
|
|
372
|
+
*/
|
|
373
|
+
const tearOut = (containerId, pageId, ev) => {
|
|
374
|
+
var _a, _b, _c;
|
|
375
|
+
if (!container)
|
|
376
|
+
return false;
|
|
377
|
+
const pages = pagesOf(containerId);
|
|
378
|
+
if (pages.length <= 1)
|
|
379
|
+
return false;
|
|
380
|
+
const label = (_b = (_a = pages.find((p) => p.id === pageId)) === null || _a === void 0 ? void 0 : _a.label) !== null && _b !== void 0 ? _b : pageId;
|
|
381
|
+
return ((_c = parentPeerOf(container, containerId)) === null || _c === void 0 ? void 0 : _c.tearOutMember(pageId, containerId, label, ev)) === true;
|
|
382
|
+
};
|
|
328
383
|
const paintStrip = (id, f, h, pages, active) => {
|
|
329
384
|
var _a, _b, _c;
|
|
330
385
|
const layer = container === null || container === void 0 ? void 0 : container.querySelector('.grafloria-html-layer');
|
|
@@ -347,7 +402,7 @@ export function attachTabsRuntime(ctx, model, container, handle) {
|
|
|
347
402
|
const key = tabStripKey(pages, active, ctx.tabsOf.get(id), rtl);
|
|
348
403
|
if (el.getAttribute('data-key') === key)
|
|
349
404
|
return;
|
|
350
|
-
paintTabStrip(el, pages, active, ctx.tabsOf.get(id), rtl, (pid) => handle.activateTab(id, pid), () => handle.selectWidget(id));
|
|
405
|
+
paintTabStrip(el, pages, active, ctx.tabsOf.get(id), rtl, (pid) => handle.activateTab(id, pid), () => handle.selectWidget(id), (pid, ev) => tearOut(id, pid, ev));
|
|
351
406
|
el.setAttribute('data-key', key);
|
|
352
407
|
};
|
|
353
408
|
const sync = (id) => {
|
|
@@ -356,7 +411,12 @@ export function attachTabsRuntime(ctx, model, container, handle) {
|
|
|
356
411
|
if (!cg || !isTabs(id))
|
|
357
412
|
return;
|
|
358
413
|
const pages = pagesOf(id);
|
|
359
|
-
|
|
414
|
+
// The page that was showing may have just been dragged out.
|
|
415
|
+
let active = (_b = ctx.activeTab.get(id)) !== null && _b !== void 0 ? _b : pages[0].id;
|
|
416
|
+
if (!pages.some((p) => p.id === active)) {
|
|
417
|
+
active = pages[0].id;
|
|
418
|
+
ctx.activeTab.set(id, active);
|
|
419
|
+
}
|
|
360
420
|
const strip = tabStripReserve(ctx.tabsOf.get(id), pages.length);
|
|
361
421
|
const f = { x: cg.position.x, y: cg.position.y, width: (_d = (_c = cg.size) === null || _c === void 0 ? void 0 : _c.width) !== null && _d !== void 0 ? _d : 0, height: (_f = (_e = cg.size) === null || _e === void 0 ? void 0 : _e.height) !== null && _f !== void 0 ? _f : 0 };
|
|
362
422
|
const inner = { width: f.width, height: Math.max(0, f.height - strip) };
|
|
@@ -407,10 +467,14 @@ export function attachTabsRuntime(ctx, model, container, handle) {
|
|
|
407
467
|
if ((meta === null || meta === void 0 ? void 0 : meta.tabs) && !ctx.tabsOf.has(id))
|
|
408
468
|
ctx.tabsOf.set(id, meta.tabs);
|
|
409
469
|
}
|
|
410
|
-
// Re-lay the pages whenever the parent moves or resizes the container
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
470
|
+
// Re-lay the pages whenever the parent moves or resizes the container —
|
|
471
|
+
// and whenever a page joins or leaves, so the strip always shows the pages
|
|
472
|
+
// that are actually there.
|
|
473
|
+
for (const ev of ['bounds:changed', 'member:added', 'member:removed']) {
|
|
474
|
+
const off = cg.on(ev, (() => sync(id)));
|
|
475
|
+
if (typeof off === 'function')
|
|
476
|
+
ctx.subscriptions.push(off);
|
|
477
|
+
}
|
|
414
478
|
sync(id);
|
|
415
479
|
}
|
|
416
480
|
}
|
|
@@ -1133,8 +1197,8 @@ export function dashboard(options) {
|
|
|
1133
1197
|
const sizing = (_h = options.sizing) !== null && _h !== void 0 ? _h : (mode === 'fluid' ? 'grow' : 'fit');
|
|
1134
1198
|
const layout = (_j = options.layout) !== null && _j !== void 0 ? _j : 'grid';
|
|
1135
1199
|
const views = options.views
|
|
1136
|
-
? options.views.map((v) => (Object.assign(Object.assign({}, v), { widgets: cloneWidgets(v.widgets) })))
|
|
1137
|
-
: [{ id: 'main', widgets: cloneWidgets((_k = options.widgets) !== null && _k !== void 0 ? _k : []) }];
|
|
1200
|
+
? options.views.map((v) => (Object.assign(Object.assign({}, v), { widgets: wrapTabPages(cloneWidgets(v.widgets)) })))
|
|
1201
|
+
: [{ id: 'main', widgets: wrapTabPages(cloneWidgets((_k = options.widgets) !== null && _k !== void 0 ? _k : [])) }];
|
|
1138
1202
|
for (const v of views)
|
|
1139
1203
|
assignCellsDeep(v.widgets, (_l = v.columns) !== null && _l !== void 0 ? _l : columns);
|
|
1140
1204
|
// -- the render spec: one custom-HTML node per widget ----------------------
|
|
@@ -485,13 +485,21 @@ interface BinderPeer {
|
|
|
485
485
|
*/
|
|
486
486
|
containsWorldExtended(x: number, y: number): boolean;
|
|
487
487
|
frameArea(): number;
|
|
488
|
-
adopt(node:
|
|
488
|
+
adopt(node: {
|
|
489
|
+
id: string;
|
|
490
|
+
}, world: {
|
|
489
491
|
x: number;
|
|
490
492
|
y: number;
|
|
491
493
|
}, pxSize: {
|
|
492
494
|
width: number;
|
|
493
495
|
height: number;
|
|
494
496
|
}): AdoptedLeg | null;
|
|
497
|
+
/**
|
|
498
|
+
* Tear a tab page out of its container and onto THIS board, driven by the
|
|
499
|
+
* press that started on its tab. Answers false when the board will not take
|
|
500
|
+
* it, so the strip can leave the press alone.
|
|
501
|
+
*/
|
|
502
|
+
tearOutMember(pageId: string, fromGroupId: string, label: string, ev: PointerEvent): boolean;
|
|
495
503
|
}
|
|
496
504
|
interface AdoptedLeg {
|
|
497
505
|
groupId: string;
|
|
@@ -448,6 +448,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
448
448
|
let placeholder = null;
|
|
449
449
|
/** Foreign tile currently adopted from another binder's gesture. */
|
|
450
450
|
let adoptedGhostId = null;
|
|
451
|
+
/** The tab page this board is currently tearing out of a container, if any. */
|
|
452
|
+
let tearing = null;
|
|
451
453
|
let glideTimer = null;
|
|
452
454
|
let ghostTimer = null;
|
|
453
455
|
/**
|
|
@@ -1091,6 +1093,11 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1091
1093
|
* the plan prototype cannot leave its board at all (cells clamp at the
|
|
1092
1094
|
* edges), so a 60px slip past the frame must not dim or delete. */
|
|
1093
1095
|
const EDGE_GRACE = 60;
|
|
1096
|
+
/** A point inside a member group's own frame (its strip and its pages). */
|
|
1097
|
+
const worldInsideGroup = (g, x, y) => {
|
|
1098
|
+
const s = sizeOf(g);
|
|
1099
|
+
return x >= g.position.x && x <= g.position.x + s.width && y >= g.position.y && y <= g.position.y + s.height;
|
|
1100
|
+
};
|
|
1094
1101
|
const worldInsideBoardGrace = (x, y) => {
|
|
1095
1102
|
if (worldInsideBoardExtended(x, y))
|
|
1096
1103
|
return true;
|
|
@@ -2014,6 +2021,14 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2014
2021
|
const geomBefore = g.startGeom.get(g.id);
|
|
2015
2022
|
const crossing = [
|
|
2016
2023
|
...sourceDisplaced,
|
|
2024
|
+
// …and the TARGET board's own displaced tiles. Dropping onto an
|
|
2025
|
+
// occupied row pushes that row down, and the ghost draws the pushed
|
|
2026
|
+
// layout — but these commands were computed and then dropped on the
|
|
2027
|
+
// floor, so nothing persisted the push. The next rebuild read the
|
|
2028
|
+
// pre-drop cells, found the arriving tile overlapping them, and pushed
|
|
2029
|
+
// IT to the bottom instead: the drop landed a whole row away from the
|
|
2030
|
+
// ghost that promised it ("it goes in as an extra row").
|
|
2031
|
+
...fin.commands,
|
|
2017
2032
|
new RemoveFromGroupCommand(group.id, g.id),
|
|
2018
2033
|
new AddToGroupCommand(targetGroupId, g.id),
|
|
2019
2034
|
...buildCommitCommands([
|
|
@@ -2116,7 +2131,65 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2116
2131
|
return best;
|
|
2117
2132
|
};
|
|
2118
2133
|
/** This binder's side of an adoption: enter gateless, then live-push. */
|
|
2119
|
-
|
|
2134
|
+
/**
|
|
2135
|
+
* Put an ARRIVING tile as close to the pointer as the board allows. The
|
|
2136
|
+
* exact cell is often refused because a SECTION is a locked tile and the
|
|
2137
|
+
* incoming tile overlaps it (E4b), and a refused placement left the tile
|
|
2138
|
+
* wherever it entered — the bottom row. That read as "it did not drop where
|
|
2139
|
+
* I put it". Slide along the row instead, nearest first, so a tile that
|
|
2140
|
+
* cannot take the cell under the pointer still lands beside it.
|
|
2141
|
+
*/
|
|
2142
|
+
const placeNear = (id, x, y, w) => {
|
|
2143
|
+
// `changed:false` means BOTH "refused" and "it is already there", and
|
|
2144
|
+
// reading the second as the first made the search walk straight past the
|
|
2145
|
+
// right answer: parked on the nearest legal cell, the next pointer move
|
|
2146
|
+
// re-tried the wanted cell (refused), re-tried the cell it was ON (no
|
|
2147
|
+
// change, read as refused) and then took a FARTHER one — so the tile
|
|
2148
|
+
// oscillated for the whole drag and settled wherever the last swing left
|
|
2149
|
+
// it. Ask where the tile IS, not whether the call moved it.
|
|
2150
|
+
const at = (cx, cy) => {
|
|
2151
|
+
const i = engine.getItem(id);
|
|
2152
|
+
return !!i && i.x === cx && i.y === cy;
|
|
2153
|
+
};
|
|
2154
|
+
const maxX = Math.max(0, columns - w);
|
|
2155
|
+
const scanRow = (cy) => {
|
|
2156
|
+
if (at(x, cy))
|
|
2157
|
+
return true;
|
|
2158
|
+
if (engine.moveCheck(id, x, cy, { gate: false }).changed)
|
|
2159
|
+
return true;
|
|
2160
|
+
for (let d = 1; d <= columns; d++) {
|
|
2161
|
+
for (const cx of [x - d, x + d]) {
|
|
2162
|
+
if (cx < 0 || cx > maxX)
|
|
2163
|
+
continue;
|
|
2164
|
+
if (at(cx, cy))
|
|
2165
|
+
return true;
|
|
2166
|
+
if (engine.moveCheck(id, cx, cy, { gate: false }).changed)
|
|
2167
|
+
return true;
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
return false;
|
|
2171
|
+
};
|
|
2172
|
+
if (scanRow(y))
|
|
2173
|
+
return true;
|
|
2174
|
+
// EVERY column on that row refused — which is what a locked section
|
|
2175
|
+
// spanning the full width does, and there are plenty of those. Sliding
|
|
2176
|
+
// sideways can never clear it, so try the rows either side, nearest first.
|
|
2177
|
+
// Without this the drop either fell to the bottom of the board or, when
|
|
2178
|
+
// the board had no room down there either, did nothing at all.
|
|
2179
|
+
const reach = Math.max(1, rows()) + 2;
|
|
2180
|
+
for (let d = 1; d <= reach; d++) {
|
|
2181
|
+
for (const cy of [y - d, y + d]) {
|
|
2182
|
+
if (cy < 0)
|
|
2183
|
+
continue;
|
|
2184
|
+
if (scanRow(cy))
|
|
2185
|
+
return true;
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
return false;
|
|
2189
|
+
};
|
|
2190
|
+
const adopt = (
|
|
2191
|
+
// Only the id is used: a tab page arriving here is a GROUP, not a node.
|
|
2192
|
+
node, world, pxSize) => {
|
|
2120
2193
|
if (disposed)
|
|
2121
2194
|
return null;
|
|
2122
2195
|
const f = frame();
|
|
@@ -2153,7 +2226,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2153
2226
|
adoptedGhostId = node.id;
|
|
2154
2227
|
const tl = centredTopLeft(world.x, world.y, span);
|
|
2155
2228
|
const cell0 = pointToCell(tl.x, tl.y, f, gg, rows(), span.w);
|
|
2156
|
-
|
|
2229
|
+
placeNear(node.id, cell0.x, cell0.y, span.w);
|
|
2157
2230
|
armGlide();
|
|
2158
2231
|
project();
|
|
2159
2232
|
syncPlaceholder();
|
|
@@ -2165,7 +2238,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2165
2238
|
return;
|
|
2166
2239
|
const tlm = centredTopLeft(w.x, w.y, { w: item.w, h: item.h });
|
|
2167
2240
|
const cell = pointToCell(tlm.x, tlm.y, frame(), geom(), rows(), item.w);
|
|
2168
|
-
if (
|
|
2241
|
+
if (placeNear(node.id, cell.x, cell.y, item.w))
|
|
2169
2242
|
project();
|
|
2170
2243
|
syncPlaceholder();
|
|
2171
2244
|
},
|
|
@@ -2198,6 +2271,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2198
2271
|
};
|
|
2199
2272
|
const selfPeer = {
|
|
2200
2273
|
group,
|
|
2274
|
+
tearOutMember: (pageId, fromGroupId, label, ev) => beginTearOut(pageId, fromGroupId, label, ev),
|
|
2201
2275
|
clearSelection: () => {
|
|
2202
2276
|
var _a;
|
|
2203
2277
|
if (selectedId === undefined)
|
|
@@ -2735,6 +2809,124 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2735
2809
|
};
|
|
2736
2810
|
api.container.addEventListener('focusin', onFocusIn);
|
|
2737
2811
|
api.container.addEventListener('keydown', onKey);
|
|
2812
|
+
// -- tab tear-out -----------------------------------------------------------
|
|
2813
|
+
/**
|
|
2814
|
+
* A press that TRAVELLED on a tab drags the whole page onto this board, the
|
|
2815
|
+
* way dragging a VS Code tab out of its group makes a group of its own. The
|
|
2816
|
+
* page is a GROUP, so it rides the same `adopt` leg a widget uses — entry at
|
|
2817
|
+
* the bottom, `placeNear` under the pointer, the dashed placeholder drawn
|
|
2818
|
+
* from the live cell — and the commit is one batch: the cell and frame it
|
|
2819
|
+
* landed on, out of the container, into this board, plus every tile this
|
|
2820
|
+
* board had to push aside.
|
|
2821
|
+
*
|
|
2822
|
+
* Like VS Code, the page itself does not follow the pointer; a chip carrying
|
|
2823
|
+
* the tab's label does, and the placeholder shows where the drop will land.
|
|
2824
|
+
*/
|
|
2825
|
+
const beginTearOut = (pageId, fromGroupId, label, ev) => {
|
|
2826
|
+
var _a, _b, _c;
|
|
2827
|
+
if (disposed || gesture || slabGesture || isStatic || tearing)
|
|
2828
|
+
return false;
|
|
2829
|
+
const pg = diagram.getGroup(pageId);
|
|
2830
|
+
const from = diagram.getGroup(fromGroupId);
|
|
2831
|
+
if (!pg || !from || !engine.getItem(fromGroupId))
|
|
2832
|
+
return false;
|
|
2833
|
+
const size = sizeOf(pg);
|
|
2834
|
+
const frameBefore = frameOfGroup(pg);
|
|
2835
|
+
const cellBefore = (_b = cellFromGridItem((_a = pg.getMetadata) === null || _a === void 0 ? void 0 : _a.call(pg, 'gridItem'))) !== null && _b !== void 0 ? _b : {
|
|
2836
|
+
x: 0,
|
|
2837
|
+
y: 0,
|
|
2838
|
+
w: 1,
|
|
2839
|
+
h: 1,
|
|
2840
|
+
};
|
|
2841
|
+
const toWorld = (cx, cy) => {
|
|
2842
|
+
var _a;
|
|
2843
|
+
const rect = api.container.getBoundingClientRect();
|
|
2844
|
+
return ((_a = api.viewport) === null || _a === void 0 ? void 0 : _a.clientToWorld) ? api.viewport.clientToWorld(cx, cy, rect) : { x: cx - rect.left, y: cy - rect.top };
|
|
2845
|
+
};
|
|
2846
|
+
const first = toWorld(ev.clientX, ev.clientY);
|
|
2847
|
+
const leg = adopt({ id: pageId }, first, { width: size.width, height: size.height });
|
|
2848
|
+
if (!leg)
|
|
2849
|
+
return false;
|
|
2850
|
+
tearing = pageId;
|
|
2851
|
+
const doc = (_c = api.container.ownerDocument) !== null && _c !== void 0 ? _c : document;
|
|
2852
|
+
const chip = doc.createElement('div');
|
|
2853
|
+
chip.className = 'axdb-drag-chip axdb-tab-chip';
|
|
2854
|
+
chip.textContent = label;
|
|
2855
|
+
doc.body.appendChild(chip);
|
|
2856
|
+
const moveChip = (cx, cy) => {
|
|
2857
|
+
chip.style.left = `${cx + 6}px`;
|
|
2858
|
+
chip.style.top = `${cy + 6}px`;
|
|
2859
|
+
};
|
|
2860
|
+
moveChip(ev.clientX, ev.clientY);
|
|
2861
|
+
armGlide();
|
|
2862
|
+
api.render();
|
|
2863
|
+
let last = { x: ev.clientX, y: ev.clientY };
|
|
2864
|
+
const detach = () => {
|
|
2865
|
+
window.removeEventListener('pointermove', onMove, true);
|
|
2866
|
+
window.removeEventListener('pointerup', onUp, true);
|
|
2867
|
+
window.removeEventListener('pointercancel', onCancel, true);
|
|
2868
|
+
window.removeEventListener('keydown', onKey, true);
|
|
2869
|
+
};
|
|
2870
|
+
const onMove = (e) => {
|
|
2871
|
+
if (disposed)
|
|
2872
|
+
return detach();
|
|
2873
|
+
last = { x: e.clientX, y: e.clientY };
|
|
2874
|
+
moveChip(e.clientX, e.clientY);
|
|
2875
|
+
const world = toWorld(e.clientX, e.clientY);
|
|
2876
|
+
const home = worldInsideGroup(from, world.x, world.y);
|
|
2877
|
+
// Over its own container it goes home: a tab dragged around its own
|
|
2878
|
+
// strip must not tear itself out.
|
|
2879
|
+
chip.classList.toggle('axdb-out', home || !worldInsideBoardGrace(world.x, world.y));
|
|
2880
|
+
if (!home)
|
|
2881
|
+
leg.move(world);
|
|
2882
|
+
api.render();
|
|
2883
|
+
};
|
|
2884
|
+
const finish = (commit) => {
|
|
2885
|
+
var _a, _b;
|
|
2886
|
+
detach();
|
|
2887
|
+
chip.remove();
|
|
2888
|
+
tearing = null;
|
|
2889
|
+
if (disposed)
|
|
2890
|
+
return;
|
|
2891
|
+
const world = toWorld(last.x, last.y);
|
|
2892
|
+
const home = worldInsideGroup(from, world.x, world.y);
|
|
2893
|
+
if (!commit || home || !worldInsideBoardGrace(world.x, world.y)) {
|
|
2894
|
+
leg.abort();
|
|
2895
|
+
disarmGlideSoon();
|
|
2896
|
+
api.renderNow();
|
|
2897
|
+
(_a = options.onGesture) === null || _a === void 0 ? void 0 : _a.call(options, { type: 'cancel', kind: 'move', nodeId: pageId, changed: false });
|
|
2898
|
+
return;
|
|
2899
|
+
}
|
|
2900
|
+
const fin = leg.finalize();
|
|
2901
|
+
if (!fin) {
|
|
2902
|
+
disarmGlideSoon();
|
|
2903
|
+
api.renderNow();
|
|
2904
|
+
return;
|
|
2905
|
+
}
|
|
2906
|
+
const changed = execute('Move tab out', [
|
|
2907
|
+
...fin.commands,
|
|
2908
|
+
new SetGroupCellCommand(pageId, cellBefore, fin.cell, frameBefore, fin.rect),
|
|
2909
|
+
new RemoveFromGroupCommand(fromGroupId, pageId),
|
|
2910
|
+
new AddToGroupCommand(group.id, pageId),
|
|
2911
|
+
]);
|
|
2912
|
+
disarmGlideSoon();
|
|
2913
|
+
enforceBoardHeight();
|
|
2914
|
+
persistLayouts();
|
|
2915
|
+
api.renderNow();
|
|
2916
|
+
(_b = options.onGesture) === null || _b === void 0 ? void 0 : _b.call(options, { type: 'commit', kind: 'move', nodeId: pageId, changed });
|
|
2917
|
+
};
|
|
2918
|
+
const onUp = () => finish(true);
|
|
2919
|
+
const onCancel = () => finish(false);
|
|
2920
|
+
const onKey = (e) => {
|
|
2921
|
+
if (e.key === 'Escape')
|
|
2922
|
+
finish(false);
|
|
2923
|
+
};
|
|
2924
|
+
window.addEventListener('pointermove', onMove, true);
|
|
2925
|
+
window.addEventListener('pointerup', onUp, true);
|
|
2926
|
+
window.addEventListener('pointercancel', onCancel, true);
|
|
2927
|
+
window.addEventListener('keydown', onKey, true);
|
|
2928
|
+
return true;
|
|
2929
|
+
};
|
|
2738
2930
|
// -- palette drag-in --------------------------------------------------------
|
|
2739
2931
|
const beginPaletteDrag = (node, spec, event) => {
|
|
2740
2932
|
var _a, _b;
|
|
@@ -3153,6 +3345,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3153
3345
|
api.container.removeEventListener('keydown', onKey);
|
|
3154
3346
|
hostObserver.disconnect();
|
|
3155
3347
|
containerObserver === null || containerObserver === void 0 ? void 0 : containerObserver.disconnect();
|
|
3348
|
+
tearing = null;
|
|
3156
3349
|
for (const off of subs)
|
|
3157
3350
|
off();
|
|
3158
3351
|
// The corner handles are THIS binder's affordance: a board re-bound as a
|
|
@@ -3,7 +3,7 @@ export { bindDashboardSplit, SetSplitTreeCommand, SPLIT_TREE_KEY, type Dashboard
|
|
|
3
3
|
export { projectSplit, dividersOf, addSplitLeaf, removeSplitLeaf, insertSplitLeaf, moveSplitDivider, splitFromCells, cellsFromSplit, splitLeaves, type SplitNode, type SplitGroup, type SplitLeaf, type SplitDir, type SplitSide, type SplitDivider, } from './split-layout.js';
|
|
4
4
|
export { rowHeightFor, boardHeightFor, columnUnitFor, cellToRect, pointToCell, sizeToSpan, spanWidthPx, gridItemFromCell, cellFromGridItem, buildCommitCommands, type CellRect, type WorldRect, type DashboardGridGeometry, type TileDelta, } from './grid-mapping.js';
|
|
5
5
|
export { ensureDashboardKitStyles, DASHBOARD_KIT_STYLE_ID } from './styles.js';
|
|
6
|
-
export { paintTabStrip, tabStripReserve, tabStripKey, TAB_STRIP_HEIGHT, type TabsOptions, type TabPage } from './tabs.js';
|
|
6
|
+
export { paintTabStrip, tabStripReserve, tabStripKey, TAB_STRIP_HEIGHT, TAB_DRAG_THRESHOLD, type TabsOptions, type TabPage } from './tabs.js';
|
|
7
7
|
export { normalizeCaption, captionReserve, captionBandHeight, paintCaptionBand, CAPTION_HEIGHT, CAPTION_HEIGHT_SUBTITLE, CAPTION_HEIGHT_TIGHT, CAPTION_PASS_THROUGH, type SectionCaption, type SectionCaptionOptions, type SectionCaptionAction, type SectionCaptionFont, } from './caption.js';
|
|
8
8
|
export { dashboard, type DashboardOptions, type DashboardSpec, type DashboardSnapshot, type DashboardHandle, type DashboardViewSpec, type DashboardWidgetSpec, type WidgetHandle, } from './dashboard.js';
|
|
9
9
|
export { defaultWidgetRenderer, renderKpiWidget, renderLineWidget, renderBarWidget, renderDonutWidget, renderFunnelWidget, renderTableWidget, BUILT_IN_WIDGET_KINDS, type WidgetRenderer, type KpiWidgetData, type LineWidgetData, type LineSeries, type BarWidgetData, type DonutWidgetData, type FunnelWidgetData, type TableWidgetData, } from './widgets.js';
|
|
@@ -3,7 +3,7 @@ export { bindDashboardSplit, SetSplitTreeCommand, SPLIT_TREE_KEY, } from './spli
|
|
|
3
3
|
export { projectSplit, dividersOf, addSplitLeaf, removeSplitLeaf, insertSplitLeaf, moveSplitDivider, splitFromCells, cellsFromSplit, splitLeaves, } from './split-layout.js';
|
|
4
4
|
export { rowHeightFor, boardHeightFor, columnUnitFor, cellToRect, pointToCell, sizeToSpan, spanWidthPx, gridItemFromCell, cellFromGridItem, buildCommitCommands, } from './grid-mapping.js';
|
|
5
5
|
export { ensureDashboardKitStyles, DASHBOARD_KIT_STYLE_ID } from './styles.js';
|
|
6
|
-
export { paintTabStrip, tabStripReserve, tabStripKey, TAB_STRIP_HEIGHT } from './tabs.js';
|
|
6
|
+
export { paintTabStrip, tabStripReserve, tabStripKey, TAB_STRIP_HEIGHT, TAB_DRAG_THRESHOLD } from './tabs.js';
|
|
7
7
|
export { normalizeCaption, captionReserve, captionBandHeight, paintCaptionBand, CAPTION_HEIGHT, CAPTION_HEIGHT_SUBTITLE, CAPTION_HEIGHT_TIGHT, CAPTION_PASS_THROUGH, } from './caption.js';
|
|
8
8
|
// The DATA-FIRST authoring API (the erDiagram/umlDiagram equivalent).
|
|
9
9
|
export { dashboard, } from './dashboard.js';
|
|
@@ -1251,6 +1251,9 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
1251
1251
|
hasItem: (id) => { var _a; return ((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id); },
|
|
1252
1252
|
memberCell: (id) => handle.cellOf(id),
|
|
1253
1253
|
resizeMemberBy: () => ({ changed: false }),
|
|
1254
|
+
// A split board has no cells to drop a torn-out page into: it refuses,
|
|
1255
|
+
// and the press stays a plain tab click.
|
|
1256
|
+
tearOutMember: () => false,
|
|
1254
1257
|
containsWorld: (x, y) => worldInsideBoard(x, y),
|
|
1255
1258
|
containsWorldExtended: (x, y) => worldInsideBoard(x, y),
|
|
1256
1259
|
frameArea: () => {
|
|
@@ -111,6 +111,27 @@ const CSS = `
|
|
|
111
111
|
(the same signal a tile ghost gives outside the board). Without this rule
|
|
112
112
|
the class was set and nothing showed — a refusal a user could not see. */
|
|
113
113
|
.axdb-drag-chip.axdb-out { opacity: .35; filter: grayscale(.7) drop-shadow(0 8px 14px rgba(16, 24, 40, .2)); }
|
|
114
|
+
/* A tab dragged off its strip: VS Code carries the TAB, not the editor, so
|
|
115
|
+
what follows the pointer is a chip wearing the tab's own label. */
|
|
116
|
+
.axdb-tab-chip {
|
|
117
|
+
padding: 5px 12px;
|
|
118
|
+
max-width: 220px;
|
|
119
|
+
overflow: hidden;
|
|
120
|
+
text-overflow: ellipsis;
|
|
121
|
+
white-space: nowrap;
|
|
122
|
+
font: 500 12px/1.4 var(--axdb-font, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif);
|
|
123
|
+
color: var(--axdb-tabs-on-fg, #1f2430);
|
|
124
|
+
background: var(--axdb-tabs-on-bg, #fff);
|
|
125
|
+
border-radius: var(--axdb-rs-radius, 3px);
|
|
126
|
+
box-shadow: 0 0 0 1px rgba(31, 36, 48, .12);
|
|
127
|
+
}
|
|
128
|
+
@media (prefers-color-scheme: dark) {
|
|
129
|
+
.axdb-tab-chip {
|
|
130
|
+
color: var(--axdb-tabs-on-fg, #eceef4);
|
|
131
|
+
background: var(--axdb-tabs-on-bg, #1a1d25);
|
|
132
|
+
box-shadow: 0 0 0 1px rgba(236, 238, 244, .16);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
114
135
|
|
|
115
136
|
/* ===========================================================================
|
|
116
137
|
BUILT-IN WIDGET CARDS — what widgets.ts paints when a page writes no
|
|
@@ -25,6 +25,12 @@ export interface TabPage {
|
|
|
25
25
|
label: string;
|
|
26
26
|
}
|
|
27
27
|
export declare const TAB_STRIP_HEIGHT = 30;
|
|
28
|
+
/**
|
|
29
|
+
* Travel, in px, that turns a press on a tab from a click into a drag. VS Code
|
|
30
|
+
* uses the platform drag threshold; 4 px is the same order and keeps a shaky
|
|
31
|
+
* click a click.
|
|
32
|
+
*/
|
|
33
|
+
export declare const TAB_DRAG_THRESHOLD = 4;
|
|
28
34
|
/** Pixels the pages give up at the top of the container. */
|
|
29
35
|
export declare function tabStripReserve(o: TabsOptions | undefined, pageCount: number): number;
|
|
30
36
|
/**
|
|
@@ -33,4 +39,11 @@ export declare function tabStripReserve(o: TabsOptions | undefined, pageCount: n
|
|
|
33
39
|
* rebuild — and so a `click` listener survives between switches.
|
|
34
40
|
*/
|
|
35
41
|
export declare function tabStripKey(pages: TabPage[], activeId: string, o: TabsOptions | undefined, rtl: boolean): string;
|
|
36
|
-
export declare function paintTabStrip(strip: HTMLElement, pages: TabPage[], activeId: string, o: TabsOptions | undefined, rtl: boolean, onPick: (id: string) => void, onSelectContainer?: () => void
|
|
42
|
+
export declare function paintTabStrip(strip: HTMLElement, pages: TabPage[], activeId: string, o: TabsOptions | undefined, rtl: boolean, onPick: (id: string) => void, onSelectContainer?: () => void,
|
|
43
|
+
/**
|
|
44
|
+
* The tab is the PAGE's drag handle, as it is in VS Code: press one and
|
|
45
|
+
* travel, and the whole page leaves — not the widget under the pointer,
|
|
46
|
+
* which used to be the only way to drag anything out and left the tab
|
|
47
|
+
* behind pointing at an empty page.
|
|
48
|
+
*/
|
|
49
|
+
onDrag?: (pageId: string, ev: PointerEvent) => boolean): void;
|
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
* binder's.
|
|
10
10
|
*/
|
|
11
11
|
export const TAB_STRIP_HEIGHT = 30;
|
|
12
|
+
/**
|
|
13
|
+
* Travel, in px, that turns a press on a tab from a click into a drag. VS Code
|
|
14
|
+
* uses the platform drag threshold; 4 px is the same order and keeps a shaky
|
|
15
|
+
* click a click.
|
|
16
|
+
*/
|
|
17
|
+
export const TAB_DRAG_THRESHOLD = 4;
|
|
12
18
|
/** Pixels the pages give up at the top of the container. */
|
|
13
19
|
export function tabStripReserve(o, pageCount) {
|
|
14
20
|
var _a;
|
|
@@ -24,8 +30,19 @@ export function tabStripReserve(o, pageCount) {
|
|
|
24
30
|
export function tabStripKey(pages, activeId, o, rtl) {
|
|
25
31
|
return JSON.stringify([pages, activeId, o !== null && o !== void 0 ? o : null, rtl]);
|
|
26
32
|
}
|
|
27
|
-
export function paintTabStrip(strip, pages, activeId, o, rtl, onPick, onSelectContainer
|
|
33
|
+
export function paintTabStrip(strip, pages, activeId, o, rtl, onPick, onSelectContainer,
|
|
34
|
+
/**
|
|
35
|
+
* The tab is the PAGE's drag handle, as it is in VS Code: press one and
|
|
36
|
+
* travel, and the whole page leaves — not the widget under the pointer,
|
|
37
|
+
* which used to be the only way to drag anything out and left the tab
|
|
38
|
+
* behind pointing at an empty page.
|
|
39
|
+
*/
|
|
40
|
+
onDrag) {
|
|
28
41
|
const doc = strip.ownerDocument;
|
|
42
|
+
// Set by a tab drag, read by the click that trails it. Strip-level so it
|
|
43
|
+
// survives between the two listeners, cleared on the next press so an
|
|
44
|
+
// aborted drag never eats a later click.
|
|
45
|
+
let suppressClick = false;
|
|
29
46
|
strip.className = 'axdb-tabs';
|
|
30
47
|
if (o === null || o === void 0 ? void 0 : o.className)
|
|
31
48
|
for (const c of o.className.split(/\s+/).filter(Boolean))
|
|
@@ -60,8 +77,41 @@ export function paintTabStrip(strip, pages, activeId, o, rtl, onPick, onSelectCo
|
|
|
60
77
|
b.tabIndex = p.id === activeId ? 0 : -1;
|
|
61
78
|
b.textContent = p.label;
|
|
62
79
|
b.title = p.label;
|
|
80
|
+
b.addEventListener('pointerdown', (e) => {
|
|
81
|
+
const pe = e;
|
|
82
|
+
if (pe.button !== undefined && pe.button > 0)
|
|
83
|
+
return;
|
|
84
|
+
suppressClick = false;
|
|
85
|
+
if (!onDrag)
|
|
86
|
+
return;
|
|
87
|
+
const from = { x: pe.clientX, y: pe.clientY };
|
|
88
|
+
const stop = () => {
|
|
89
|
+
doc.removeEventListener('pointermove', move, true);
|
|
90
|
+
doc.removeEventListener('pointerup', stop, true);
|
|
91
|
+
doc.removeEventListener('pointercancel', stop, true);
|
|
92
|
+
};
|
|
93
|
+
const move = (m) => {
|
|
94
|
+
const pm = m;
|
|
95
|
+
if (Math.abs(pm.clientX - from.x) < TAB_DRAG_THRESHOLD &&
|
|
96
|
+
Math.abs(pm.clientY - from.y) < TAB_DRAG_THRESHOLD)
|
|
97
|
+
return;
|
|
98
|
+
stop();
|
|
99
|
+
// A press that TRAVELLED is a drag: the click that may follow it must
|
|
100
|
+
// not also switch the page — but only if the drag was actually TAKEN.
|
|
101
|
+
// A board that cannot place the page (a split pane, a container down
|
|
102
|
+
// to its last page) refuses, and then the press is still a click.
|
|
103
|
+
suppressClick = onDrag(p.id, pm) === true;
|
|
104
|
+
};
|
|
105
|
+
doc.addEventListener('pointermove', move, true);
|
|
106
|
+
doc.addEventListener('pointerup', stop, true);
|
|
107
|
+
doc.addEventListener('pointercancel', stop, true);
|
|
108
|
+
});
|
|
63
109
|
b.addEventListener('click', (e) => {
|
|
64
110
|
e.stopPropagation();
|
|
111
|
+
if (suppressClick) {
|
|
112
|
+
suppressClick = false;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
65
115
|
onPick(p.id);
|
|
66
116
|
});
|
|
67
117
|
b.addEventListener('keydown', (e) => {
|