@design.estate/dees-catalog 3.98.1 → 3.99.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.
- package/dist_bundle/bundle.js +192 -26
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/00group-chart/dees-chart-area/component.d.ts +22 -0
- package/dist_ts_web/elements/00group-chart/dees-chart-area/component.js +175 -3
- package/dist_ts_web/elements/00group-chart/dees-chart-area/styles.js +11 -1
- package/dist_ts_web/elements/00group-chart/dees-chart-area/template.js +4 -2
- package/dist_ts_web/elements/00group-dataview/dees-table/dees-table.d.ts +2 -0
- package/dist_ts_web/elements/00group-dataview/dees-table/dees-table.js +23 -23
- package/package.json +2 -2
- package/readme.md +10 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/00group-chart/dees-chart-area/component.ts +164 -1
- package/ts_web/elements/00group-chart/dees-chart-area/styles.ts +10 -0
- package/ts_web/elements/00group-chart/dees-chart-area/template.ts +3 -1
- package/ts_web/elements/00group-dataview/dees-table/dees-table.ts +28 -20
- package/dist_watch/bundle.js +0 -235637
- package/dist_watch/bundle.js.map +0 -7
- package/dist_watch/index.html +0 -28
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@design.estate/dees-catalog',
|
|
6
|
-
version: '3.
|
|
6
|
+
version: '3.99.1',
|
|
7
7
|
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
|
|
8
8
|
}
|
|
@@ -33,6 +33,11 @@ export type TChartPriceLine = 'avg' | 'max';
|
|
|
33
33
|
|
|
34
34
|
export const CHART_LEGEND_STATS: TChartLegendStat[] = ['latest', 'min', 'max', 'avg'];
|
|
35
35
|
|
|
36
|
+
export interface IChartSelectedRange {
|
|
37
|
+
from: number;
|
|
38
|
+
to: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
36
41
|
declare global {
|
|
37
42
|
interface HTMLElementTagNameMap {
|
|
38
43
|
'dees-chart-area': DeesChartArea;
|
|
@@ -100,6 +105,12 @@ export class DeesChartArea extends DeesElement {
|
|
|
100
105
|
@property({ type: Array })
|
|
101
106
|
accessor chartLines: TChartPriceLine[] = ['avg', 'max'];
|
|
102
107
|
|
|
108
|
+
|
|
109
|
+
@property({ type: Boolean })
|
|
110
|
+
accessor rangeSelectionEnabled = false;
|
|
111
|
+
|
|
112
|
+
@property({ attribute: false })
|
|
113
|
+
accessor selectedRange: IChartSelectedRange | null = null;
|
|
103
114
|
private internalChartData: ChartSeriesConfig = [];
|
|
104
115
|
private autoScrollTimer: number | null = null;
|
|
105
116
|
private lcBundle: ILightweightChartsBundle | null = null;
|
|
@@ -110,6 +121,62 @@ export class DeesChartArea extends DeesElement {
|
|
|
110
121
|
private pendingRealtimePrune = false;
|
|
111
122
|
private crosshairMoveHandler: ((param: MouseEventParams) => void) | null = null;
|
|
112
123
|
private chartContainer: HTMLDivElement | null = null;
|
|
124
|
+
private rangeOverlay: HTMLDivElement | null = null;
|
|
125
|
+
private rangeResizeObserver: ResizeObserver | null = null;
|
|
126
|
+
private rangeDrag: { pointerId: number; startX: number; startMs: number } | null = null;
|
|
127
|
+
private readonly rangePointerDownHandler = (event: PointerEvent): void => {
|
|
128
|
+
if (!this.rangeSelectionEnabled || event.button !== 0 || !this.chartContainer) return;
|
|
129
|
+
const x = this.getRangePointerX(event);
|
|
130
|
+
const timeMs = this.coordinateToEpochMs(x);
|
|
131
|
+
if (timeMs === null) return;
|
|
132
|
+
event.preventDefault();
|
|
133
|
+
this.rangeDrag = { pointerId: event.pointerId, startX: x, startMs: timeMs };
|
|
134
|
+
try { this.chartContainer.setPointerCapture(event.pointerId); } catch { /* synthetic event */ }
|
|
135
|
+
window.addEventListener('keydown', this.rangeKeydownHandler);
|
|
136
|
+
this.paintRangeOverlay(x, x);
|
|
137
|
+
};
|
|
138
|
+
private readonly rangePointerMoveHandler = (event: PointerEvent): void => {
|
|
139
|
+
if (!this.rangeDrag || event.pointerId !== this.rangeDrag.pointerId) return;
|
|
140
|
+
event.preventDefault();
|
|
141
|
+
this.paintRangeOverlay(this.rangeDrag.startX, this.getRangePointerX(event));
|
|
142
|
+
};
|
|
143
|
+
private readonly rangePointerUpHandler = (event: PointerEvent): void => {
|
|
144
|
+
const drag = this.rangeDrag;
|
|
145
|
+
if (!drag || event.pointerId !== drag.pointerId) return;
|
|
146
|
+
const endX = this.getRangePointerX(event);
|
|
147
|
+
const endMs = this.coordinateToEpochMs(endX);
|
|
148
|
+
this.finishRangeDrag(event.pointerId);
|
|
149
|
+
if (endMs === null || Math.abs(endX - drag.startX) < 4) {
|
|
150
|
+
this.syncRangeOverlay();
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const from = Math.min(drag.startMs, endMs);
|
|
154
|
+
const to = Math.max(drag.startMs, endMs);
|
|
155
|
+
if (!Number.isSafeInteger(from) || !Number.isSafeInteger(to) || from >= to) {
|
|
156
|
+
this.syncRangeOverlay();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
this.dispatchEvent(new CustomEvent<IChartSelectedRange>('range-change', {
|
|
160
|
+
detail: { from, to },
|
|
161
|
+
bubbles: true,
|
|
162
|
+
composed: true,
|
|
163
|
+
}));
|
|
164
|
+
this.syncRangeOverlay();
|
|
165
|
+
};
|
|
166
|
+
private readonly rangePointerCancelHandler = (event: PointerEvent): void => {
|
|
167
|
+
if (!this.rangeDrag || event.pointerId !== this.rangeDrag.pointerId) return;
|
|
168
|
+
this.finishRangeDrag(event.pointerId);
|
|
169
|
+
this.syncRangeOverlay();
|
|
170
|
+
};
|
|
171
|
+
private readonly rangeKeydownHandler = (event: KeyboardEvent): void => {
|
|
172
|
+
if (event.key !== 'Escape' || !this.rangeDrag) return;
|
|
173
|
+
const pointerId = this.rangeDrag.pointerId;
|
|
174
|
+
this.finishRangeDrag(pointerId);
|
|
175
|
+
this.syncRangeOverlay();
|
|
176
|
+
};
|
|
177
|
+
private readonly rangeVisibleTimeChangeHandler = (): void => {
|
|
178
|
+
this.syncRangeOverlay();
|
|
179
|
+
};
|
|
113
180
|
private readonly chartMouseLeaveHandler = () => {
|
|
114
181
|
this.chart?.clearCrosshairPosition();
|
|
115
182
|
this.handleCrosshairCleared();
|
|
@@ -122,11 +189,15 @@ export class DeesChartArea extends DeesElement {
|
|
|
122
189
|
this.stopAutoScroll();
|
|
123
190
|
const chart = this.chart;
|
|
124
191
|
const crosshairMoveHandler = this.crosshairMoveHandler;
|
|
125
|
-
this.chart = null;
|
|
126
192
|
this.crosshairMoveHandler = null;
|
|
127
193
|
this.chartContainer?.removeEventListener('mouseleave', this.chartMouseLeaveHandler);
|
|
194
|
+
this.teardownRangeSelection();
|
|
195
|
+
this.chart = null;
|
|
196
|
+
this.rangeResizeObserver?.disconnect();
|
|
197
|
+
this.rangeResizeObserver = null;
|
|
128
198
|
this.chartContainer = null;
|
|
129
199
|
this.tooltipEl = null;
|
|
200
|
+
this.rangeOverlay = null;
|
|
130
201
|
this.crosshairActive = false;
|
|
131
202
|
this.pendingRealtimePrune = false;
|
|
132
203
|
if (crosshairMoveHandler && chart) {
|
|
@@ -155,6 +226,90 @@ export class DeesChartArea extends DeesElement {
|
|
|
155
226
|
|
|
156
227
|
// --- Helpers ---
|
|
157
228
|
|
|
229
|
+
private getRangePointerX(event: PointerEvent): number {
|
|
230
|
+
const rect = this.chartContainer!.getBoundingClientRect();
|
|
231
|
+
return Math.min(Math.max(event.clientX - rect.left, 0), rect.width);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private coordinateToEpochMs(coordinateArg: number): number | null {
|
|
235
|
+
const time = this.chart?.timeScale().coordinateToTime(coordinateArg);
|
|
236
|
+
if (typeof time !== 'number' || !Number.isFinite(time)) return null;
|
|
237
|
+
const epochMs = Math.round(time * 1000);
|
|
238
|
+
return Number.isSafeInteger(epochMs) && epochMs >= 0 ? epochMs : null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private paintRangeOverlay(firstXArg: number, secondXArg: number): void {
|
|
242
|
+
if (!this.rangeOverlay) return;
|
|
243
|
+
const left = Math.min(firstXArg, secondXArg);
|
|
244
|
+
const width = Math.abs(secondXArg - firstXArg);
|
|
245
|
+
this.rangeOverlay.style.display = 'block';
|
|
246
|
+
this.rangeOverlay.style.left = `${left}px`;
|
|
247
|
+
this.rangeOverlay.style.width = `${Math.max(width, 1)}px`;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private finishRangeDrag(pointerIdArg: number): void {
|
|
251
|
+
this.rangeDrag = null;
|
|
252
|
+
window.removeEventListener('keydown', this.rangeKeydownHandler);
|
|
253
|
+
try { this.chartContainer?.releasePointerCapture(pointerIdArg); } catch { /* already released */ }
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private setupRangeSelection(): void {
|
|
257
|
+
if (!this.chartContainer) return;
|
|
258
|
+
this.rangeOverlay = this.chartContainer.querySelector('.rangeSelectionOverlay');
|
|
259
|
+
this.chart?.timeScale().subscribeVisibleTimeRangeChange(this.rangeVisibleTimeChangeHandler);
|
|
260
|
+
this.chartContainer.addEventListener('pointerdown', this.rangePointerDownHandler);
|
|
261
|
+
this.chartContainer.addEventListener('pointermove', this.rangePointerMoveHandler);
|
|
262
|
+
this.chartContainer.addEventListener('pointerup', this.rangePointerUpHandler);
|
|
263
|
+
this.chartContainer.addEventListener('pointercancel', this.rangePointerCancelHandler);
|
|
264
|
+
this.chartContainer.addEventListener('lostpointercapture', this.rangePointerCancelHandler);
|
|
265
|
+
this.rangeResizeObserver = new ResizeObserver(() => this.syncRangeOverlay());
|
|
266
|
+
this.rangeResizeObserver.observe(this.chartContainer);
|
|
267
|
+
this.syncRangeOverlay();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private teardownRangeSelection(): void {
|
|
271
|
+
if (!this.chartContainer) return;
|
|
272
|
+
this.chart?.timeScale().unsubscribeVisibleTimeRangeChange(this.rangeVisibleTimeChangeHandler);
|
|
273
|
+
this.chartContainer.removeEventListener('pointerdown', this.rangePointerDownHandler);
|
|
274
|
+
this.chartContainer.removeEventListener('pointermove', this.rangePointerMoveHandler);
|
|
275
|
+
this.chartContainer.removeEventListener('pointerup', this.rangePointerUpHandler);
|
|
276
|
+
this.chartContainer.removeEventListener('pointercancel', this.rangePointerCancelHandler);
|
|
277
|
+
this.chartContainer.removeEventListener('lostpointercapture', this.rangePointerCancelHandler);
|
|
278
|
+
window.removeEventListener('keydown', this.rangeKeydownHandler);
|
|
279
|
+
if (this.rangeDrag) {
|
|
280
|
+
try { this.chartContainer.releasePointerCapture(this.rangeDrag.pointerId); } catch { /* no capture */ }
|
|
281
|
+
}
|
|
282
|
+
this.rangeDrag = null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private syncRangeOverlay(): void {
|
|
286
|
+
if (!this.rangeOverlay || !this.chart || !this.rangeSelectionEnabled || !this.selectedRange) {
|
|
287
|
+
if (this.rangeOverlay) this.rangeOverlay.style.display = 'none';
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const { from, to } = this.selectedRange;
|
|
291
|
+
if (
|
|
292
|
+
!Number.isSafeInteger(from)
|
|
293
|
+
|| !Number.isSafeInteger(to)
|
|
294
|
+
|| from < 0
|
|
295
|
+
|| to <= from
|
|
296
|
+
) {
|
|
297
|
+
this.rangeOverlay.style.display = 'none';
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const fromCoordinate = this.chart.timeScale().timeToCoordinate(
|
|
301
|
+
Math.floor(from / 1000) as UTCTimestamp,
|
|
302
|
+
);
|
|
303
|
+
const toCoordinate = this.chart.timeScale().timeToCoordinate(
|
|
304
|
+
Math.floor(to / 1000) as UTCTimestamp,
|
|
305
|
+
);
|
|
306
|
+
if (fromCoordinate === null || toCoordinate === null) {
|
|
307
|
+
this.rangeOverlay.style.display = 'none';
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
this.paintRangeOverlay(fromCoordinate, toCoordinate);
|
|
311
|
+
}
|
|
312
|
+
|
|
158
313
|
private convertDataToLC(data: Array<{ x: any; y: number }>): Array<{ time: UTCTimestamp; value: number }> {
|
|
159
314
|
const pointsByTime = new Map<number, { time: UTCTimestamp; value: number }>();
|
|
160
315
|
for (const point of data) {
|
|
@@ -580,6 +735,7 @@ export class DeesChartArea extends DeesElement {
|
|
|
580
735
|
this.internalChartData = canonicalSeries;
|
|
581
736
|
this.recreateSeries(canonicalSeries);
|
|
582
737
|
this.setupTooltip();
|
|
738
|
+
this.setupRangeSelection();
|
|
583
739
|
this.syncAutoScroll();
|
|
584
740
|
} catch (error) {
|
|
585
741
|
console.error('Failed to initialize chart:', error);
|
|
@@ -620,6 +776,13 @@ export class DeesChartArea extends DeesElement {
|
|
|
620
776
|
this.syncAutoScroll();
|
|
621
777
|
}
|
|
622
778
|
|
|
779
|
+
if (
|
|
780
|
+
changedProperties.has('selectedRange')
|
|
781
|
+
|| changedProperties.has('rangeSelectionEnabled')
|
|
782
|
+
) {
|
|
783
|
+
this.syncRangeOverlay();
|
|
784
|
+
}
|
|
785
|
+
|
|
623
786
|
if (changedProperties.has('chartLines') && this.chart) {
|
|
624
787
|
this.refreshPriceLines();
|
|
625
788
|
}
|
|
@@ -50,6 +50,16 @@ export const chartAreaStyles = [
|
|
|
50
50
|
position: absolute;
|
|
51
51
|
inset: 0 0 4px 0;
|
|
52
52
|
}
|
|
53
|
+
.rangeSelectionOverlay {
|
|
54
|
+
position: absolute;
|
|
55
|
+
display: none;
|
|
56
|
+
top: 0;
|
|
57
|
+
bottom: 0;
|
|
58
|
+
z-index: 3;
|
|
59
|
+
pointer-events: none;
|
|
60
|
+
background: color-mix(in srgb, var(--dees-color-accent, #3b82f6) 18%, transparent);
|
|
61
|
+
border-inline: 1px solid color-mix(in srgb, var(--dees-color-accent, #3b82f6) 70%, transparent);
|
|
62
|
+
}
|
|
53
63
|
.statsBar {
|
|
54
64
|
min-height: 32px;
|
|
55
65
|
padding: 4px 16px;
|
|
@@ -11,7 +11,9 @@ export const renderChartArea = (component: DeesChartArea): TemplateResult => {
|
|
|
11
11
|
<dees-icon .icon=${component.isFullPage ? 'lucide:Minimize2' : 'lucide:Maximize2'} .iconSize=${14}></dees-icon>
|
|
12
12
|
</button>
|
|
13
13
|
</div>
|
|
14
|
-
<div class="chartContainer"
|
|
14
|
+
<div class="chartContainer">
|
|
15
|
+
<div class="rangeSelectionOverlay" aria-hidden="true"></div>
|
|
16
|
+
</div>
|
|
15
17
|
${component.seriesStats.length > 0 && !component.hideLegend ? html`
|
|
16
18
|
<div slot="footer" class="statsBar">
|
|
17
19
|
${component.seriesStats.map(s => html`
|
|
@@ -116,6 +116,15 @@ export class DeesTable<T> extends DeesElement {
|
|
|
116
116
|
type: Array,
|
|
117
117
|
})
|
|
118
118
|
accessor dataActions: ITableAction<T>[] = [];
|
|
119
|
+
private readonly __searchAction: ITableAction<T> = {
|
|
120
|
+
name: 'Search',
|
|
121
|
+
iconName: 'lucide:Search',
|
|
122
|
+
type: ['header'],
|
|
123
|
+
actionFunc: async () => {
|
|
124
|
+
const searchGrid = this.shadowRoot!.querySelector('.searchGrid');
|
|
125
|
+
searchGrid!.classList.toggle('hidden');
|
|
126
|
+
},
|
|
127
|
+
};
|
|
119
128
|
|
|
120
129
|
// schema-first columns API
|
|
121
130
|
@property({ attribute: false })
|
|
@@ -854,7 +863,7 @@ export class DeesTable<T> extends DeesElement {
|
|
|
854
863
|
${this.renderSortIndicator(col)}
|
|
855
864
|
</th>`;
|
|
856
865
|
})}
|
|
857
|
-
${this.
|
|
866
|
+
${this.__getEffectiveDataActions().length > 0
|
|
858
867
|
? html`<th class="actionsCol">Actions</th>`
|
|
859
868
|
: html``}
|
|
860
869
|
</tr>
|
|
@@ -873,7 +882,7 @@ export class DeesTable<T> extends DeesElement {
|
|
|
873
882
|
@input=${(e: Event) => this.setColumnFilter(key, (e.target as HTMLInputElement).value)} />
|
|
874
883
|
</th>`;
|
|
875
884
|
})}
|
|
876
|
-
${this.
|
|
885
|
+
${this.__getEffectiveDataActions().length > 0
|
|
877
886
|
? html`<th></th>`
|
|
878
887
|
: html``}
|
|
879
888
|
</tr>`
|
|
@@ -1512,21 +1521,6 @@ export class DeesTable<T> extends DeesElement {
|
|
|
1512
1521
|
this.__syncFloatingHeader();
|
|
1513
1522
|
}
|
|
1514
1523
|
if (this.searchable) {
|
|
1515
|
-
const existing = this.dataActions.find((actionArg) => actionArg.type?.includes('header') && actionArg.name === 'Search');
|
|
1516
|
-
if (!existing) {
|
|
1517
|
-
this.dataActions.unshift({
|
|
1518
|
-
name: 'Search',
|
|
1519
|
-
iconName: 'lucide:Search',
|
|
1520
|
-
type: ['header'],
|
|
1521
|
-
actionFunc: async () => {
|
|
1522
|
-
console.log('open search');
|
|
1523
|
-
const searchGrid = this.shadowRoot!.querySelector('.searchGrid');
|
|
1524
|
-
searchGrid!.classList.toggle('hidden');
|
|
1525
|
-
}
|
|
1526
|
-
});
|
|
1527
|
-
console.log(this.dataActions);
|
|
1528
|
-
this.requestUpdate();
|
|
1529
|
-
};
|
|
1530
1524
|
// wire search inputs
|
|
1531
1525
|
this.wireSearchInputs();
|
|
1532
1526
|
}
|
|
@@ -1595,7 +1589,9 @@ export class DeesTable<T> extends DeesElement {
|
|
|
1595
1589
|
const width = window.getComputedStyle(cell).width;
|
|
1596
1590
|
if (cell.textContent.includes('Actions')) {
|
|
1597
1591
|
const neededWidth =
|
|
1598
|
-
this.
|
|
1592
|
+
this.__getEffectiveDataActions().filter((actionArg) =>
|
|
1593
|
+
actionArg.type?.includes('inRow')
|
|
1594
|
+
).length * 36;
|
|
1599
1595
|
cell.style.width = `${Math.max(neededWidth, 68)}px`;
|
|
1600
1596
|
} else {
|
|
1601
1597
|
cell.style.width = width;
|
|
@@ -2121,7 +2117,7 @@ export class DeesTable<T> extends DeesElement {
|
|
|
2121
2117
|
this.startEditing(cell.item, cell.col);
|
|
2122
2118
|
return;
|
|
2123
2119
|
}
|
|
2124
|
-
const dblAction = this.
|
|
2120
|
+
const dblAction = this.__getEffectiveDataActions().find(
|
|
2125
2121
|
(action) =>
|
|
2126
2122
|
action.type?.includes('doubleClick')
|
|
2127
2123
|
&& this.isActionRelevant(action, cell.item)
|
|
@@ -2349,9 +2345,21 @@ export class DeesTable<T> extends DeesElement {
|
|
|
2349
2345
|
);
|
|
2350
2346
|
}
|
|
2351
2347
|
|
|
2348
|
+
private __getEffectiveDataActions(): ITableAction<T>[] {
|
|
2349
|
+
if (
|
|
2350
|
+
!this.searchable
|
|
2351
|
+
|| this.dataActions.some((actionArg) =>
|
|
2352
|
+
actionArg.type?.includes('header') && actionArg.name === 'Search'
|
|
2353
|
+
)
|
|
2354
|
+
) {
|
|
2355
|
+
return this.dataActions;
|
|
2356
|
+
}
|
|
2357
|
+
return [this.__searchAction, ...this.dataActions];
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2352
2360
|
getActionsForType(typeArg: ITableAction['type'][0]) {
|
|
2353
2361
|
const actions: ITableAction[] = [];
|
|
2354
|
-
for (const action of this.
|
|
2362
|
+
for (const action of this.__getEffectiveDataActions()) {
|
|
2355
2363
|
if (!action.type?.includes(typeArg)) continue;
|
|
2356
2364
|
actions.push(action);
|
|
2357
2365
|
}
|