layerchart 2.0.0-next.19 → 2.0.0-next.20

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.
@@ -36,7 +36,7 @@
36
36
  /**
37
37
  * Props to pass to the `<text>` element for month labels.
38
38
  */
39
- monthLabel?: Partial<ComponentProps<typeof Text>>;
39
+ monthLabel?: boolean | Partial<ComponentProps<typeof Text>>;
40
40
 
41
41
  /**
42
42
  * Tooltip context to setup mouse events to show tooltip for related data
@@ -74,9 +74,9 @@
74
74
  start,
75
75
  cellSize: cellSizeProp,
76
76
  monthPath = false,
77
+ monthLabel = true,
77
78
  tooltipContext: tooltip,
78
79
  children,
79
- monthLabel,
80
80
  ...restProps
81
81
  }: CalendarPropsWithoutHTML = $props();
82
82
 
@@ -136,7 +136,11 @@
136
136
  {#if monthPath}
137
137
  {#each yearMonths as date}
138
138
  <MonthPath {date} {cellSize} {...extractLayerProps(monthPath, 'calendar-month-path')} />
139
+ {/each}
140
+ {/if}
139
141
 
142
+ {#if monthLabel}
143
+ {#each yearMonths as date}
140
144
  <Text
141
145
  x={timeWeek.count(timeYear.floor(date), timeWeek.ceil(date)) * cellSize[0]}
142
146
  y={-4}
@@ -30,7 +30,7 @@ export type CalendarPropsWithoutHTML = {
30
30
  /**
31
31
  * Props to pass to the `<text>` element for month labels.
32
32
  */
33
- monthLabel?: Partial<ComponentProps<typeof Text>>;
33
+ monthLabel?: boolean | Partial<ComponentProps<typeof Text>>;
34
34
  /**
35
35
  * Tooltip context to setup mouse events to show tooltip for related data
36
36
  */
@@ -5,6 +5,7 @@
5
5
  import type { TooltipContextValue } from './tooltip/TooltipContext.svelte';
6
6
  import { curveLinearClosed, type CurveFactory, type CurveFactoryLineOnly } from 'd3-shape';
7
7
  import {
8
+ geoPath as d3GeoPath,
8
9
  geoTransform as d3geoTransform,
9
10
  type GeoIdentityTransform,
10
11
  type GeoPermissibleObjects,
@@ -105,6 +106,10 @@
105
106
  const geoPath = $derived.by(() => {
106
107
  geojson;
107
108
  if (!projection) return;
109
+ // Only use geoCurvePath for custom curves (performance impact)
110
+ if (curve === curveLinearClosed) {
111
+ return d3GeoPath(projection);
112
+ }
108
113
  return geoCurvePath(projection, curve);
109
114
  });
110
115
 
@@ -166,8 +171,9 @@
166
171
  touchmove: restProps.ontouchmove,
167
172
  },
168
173
  deps: () => [
169
- geojson,
170
174
  projection,
175
+ geojson,
176
+ curve,
171
177
  fillKey.current,
172
178
  strokeKey.current,
173
179
  strokeWidth,
@@ -31,8 +31,8 @@
31
31
 
32
32
  <script lang="ts">
33
33
  import { timeWeek, timeMonth, timeYear } from 'd3-time';
34
- import { endOfInterval } from '../utils/date.js';
35
34
  import { cls } from '@layerstack/tailwind';
35
+ import { endOfInterval } from '@layerstack/utils';
36
36
  import { layerClass } from '../utils/attributes.js';
37
37
  import Spline, { type SplinePropsWithoutHTML } from './Spline.svelte';
38
38
 
@@ -58,7 +58,7 @@
58
58
  const startWeek = $derived(timeWeek.count(timeYear(date), date));
59
59
 
60
60
  // end of month
61
- const monthEnd = $derived(endOfInterval(date, timeMonth));
61
+ const monthEnd = $derived(endOfInterval('month', date));
62
62
  const endDayOfWeek = $derived(monthEnd.getDay());
63
63
  const endWeek = $derived(timeWeek.count(timeYear(monthEnd), monthEnd));
64
64
 
@@ -346,45 +346,53 @@
346
346
  context.restore();
347
347
  }
348
348
 
349
- // sync hit canvas with main canvas
349
+ /*
350
+ * Sync hit canvas with main canvas
351
+ */
350
352
  if (hitCanvasContext) {
351
- // scale hit canvas to match main canvas
352
- scaleCanvas(hitCanvasContext, ctx.containerWidth, ctx.containerHeight);
353
- hitCanvasContext.clearRect(0, 0, ctx.containerWidth, ctx.containerHeight);
354
-
355
- // reset and sync transform to the state after retainState components
356
- hitCanvasContext.resetTransform();
357
- hitCanvasContext.setTransform(mainTransformAfterRetain);
358
-
359
- // reset color generator
360
- colorGenerator = rgbColorGenerator();
361
-
362
353
  const inactiveMoving = !activeCanvas && transformCtx.moving;
363
-
364
- // render retainState components on hit canvas (e.g., Group)
365
- for (const c of retainStateComponents) {
366
- const componentHasEvents = c.events && Object.values(c.events).filter((d) => d).length > 0;
367
-
368
- if (componentHasEvents && !inactiveMoving && !transformCtx.dragging) {
369
- // since the transform was already applied via setTransform, skip rendering
370
- // the retainState component's transform again; proceed to its children
371
- continue;
354
+ if (disableHitCanvas || transformCtx.dragging || inactiveMoving) {
355
+ // Skip rendering hit canvas
356
+ hitCanvasContext.clearRect(0, 0, ctx.containerWidth, ctx.containerHeight);
357
+ } else {
358
+ // scale hit canvas to match main canvas
359
+ scaleCanvas(hitCanvasContext, ctx.containerWidth, ctx.containerHeight);
360
+ hitCanvasContext.clearRect(0, 0, ctx.containerWidth, ctx.containerHeight);
361
+
362
+ // reset and sync transform to the state after retainState components
363
+ hitCanvasContext.resetTransform();
364
+ hitCanvasContext.setTransform(mainTransformAfterRetain);
365
+
366
+ // reset color generator
367
+ colorGenerator = rgbColorGenerator();
368
+
369
+ // render retainState components on hit canvas (e.g., Group)
370
+ for (const c of retainStateComponents) {
371
+ const componentHasEvents =
372
+ c.events && Object.values(c.events).filter((d) => d).length > 0;
373
+
374
+ if (componentHasEvents) {
375
+ // since the transform was already applied via setTransform, skip rendering
376
+ // the retainState component's transform again; proceed to its children
377
+ continue;
378
+ }
372
379
  }
373
- }
374
380
 
375
- // render non-retainState components on hit canvas
376
- for (const c of nonRetainStateComponents) {
377
- const componentHasEvents = c.events && Object.values(c.events).filter((d) => d).length > 0;
381
+ // render non-retainState components on hit canvas
382
+ for (const c of nonRetainStateComponents) {
383
+ const componentHasEvents =
384
+ c.events && Object.values(c.events).filter((d) => d).length > 0;
378
385
 
379
- if (componentHasEvents && !inactiveMoving && !transformCtx.dragging && !disableHitCanvas) {
380
- const color = getColorStr(colorGenerator.next().value);
381
- const styleOverrides = { styles: { fill: color, stroke: color, _fillOpacity: 0.1 } };
386
+ if (componentHasEvents) {
387
+ const color = getColorStr(colorGenerator.next().value);
388
+ const styleOverrides = { styles: { fill: color, stroke: color, _fillOpacity: 0.1 } };
382
389
 
383
- hitCanvasContext.save();
384
- c.render(hitCanvasContext, styleOverrides);
385
- hitCanvasContext.restore();
390
+ hitCanvasContext.save();
391
+ c.render(hitCanvasContext, styleOverrides);
392
+ hitCanvasContext.restore();
386
393
 
387
- componentByColor.set(color, c);
394
+ componentByColor.set(color, c);
395
+ }
388
396
  }
389
397
  }
390
398
  }
@@ -0,0 +1,77 @@
1
+ import type { ClassValue } from 'svelte/elements';
2
+ import type { PatternShape } from '../components/Pattern.svelte';
3
+ export declare const DEFAULT_FILL = "rgb(0, 0, 0)";
4
+ type StyleOptions = Partial<Omit<CSSStyleDeclaration, 'fillOpacity' | 'strokeWidth' | 'opacity'> & {
5
+ fillOpacity?: number | string;
6
+ strokeWidth?: number | string;
7
+ opacity?: number | string;
8
+ }>;
9
+ export type ComputedStylesOptions = {
10
+ styles?: StyleOptions;
11
+ classes?: ClassValue | null;
12
+ };
13
+ /**
14
+ * Appends or reuses `<svg>` element below `<canvas>` to resolve CSS variables and classes (ex. `stroke: var(--color-primary)` => `stroke: rgb(...)` )
15
+ */
16
+ export declare function _getComputedStyles(canvas: HTMLCanvasElement, { styles, classes }?: ComputedStylesOptions): CSSStyleDeclaration;
17
+ export declare const getComputedStyles: any;
18
+ /** Render SVG path data onto canvas context. Supports CSS variables and classes by tranferring to hidden `<svg>` element before retrieval) */
19
+ export declare function renderPathData(ctx: CanvasRenderingContext2D, pathData: string | null | undefined, styleOptions?: ComputedStylesOptions): void;
20
+ export declare function renderText(ctx: CanvasRenderingContext2D, text: string | number | null | undefined, coords: {
21
+ x: number;
22
+ y: number;
23
+ }, styleOptions?: ComputedStylesOptions): void;
24
+ export declare function renderRect(ctx: CanvasRenderingContext2D, coords: {
25
+ x: number;
26
+ y: number;
27
+ width: number;
28
+ height: number;
29
+ }, styleOptions?: ComputedStylesOptions): void;
30
+ export declare function renderCircle(ctx: CanvasRenderingContext2D, coords: {
31
+ cx: number;
32
+ cy: number;
33
+ r: number;
34
+ }, styleOptions?: ComputedStylesOptions): void;
35
+ export declare function renderEllipse(ctx: CanvasRenderingContext2D, coords: {
36
+ cx: number;
37
+ cy: number;
38
+ rx: number;
39
+ ry: number;
40
+ }, styleOptions?: ComputedStylesOptions): void;
41
+ /** Clear canvas accounting for Canvas `context.translate(...)` */
42
+ export declare function clearCanvasContext(ctx: CanvasRenderingContext2D, options: {
43
+ containerWidth: number;
44
+ containerHeight: number;
45
+ padding: {
46
+ top: number;
47
+ bottom: number;
48
+ left: number;
49
+ right: number;
50
+ };
51
+ }): void;
52
+ /**
53
+ Scales a canvas for high DPI / retina displays.
54
+ @see: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio#examples
55
+ @see: https://web.dev/articles/canvas-hidipi
56
+ */
57
+ export declare function scaleCanvas(ctx: CanvasRenderingContext2D, width: number, height: number): {
58
+ width: number;
59
+ height: number;
60
+ };
61
+ /** Get pixel color (r,g,b,a) at canvas coordinates */
62
+ export declare function getPixelColor(ctx: CanvasRenderingContext2D, x: number, y: number): {
63
+ r: number;
64
+ g: number;
65
+ b: number;
66
+ a: number;
67
+ };
68
+ export declare function _createLinearGradient(ctx: CanvasRenderingContext2D, x0: number, y0: number, x1: number, y1: number, stops: {
69
+ offset: number;
70
+ color: string;
71
+ }[]): CanvasGradient;
72
+ /** Create linear gradient and memoize result to fix reactivity */
73
+ export declare const createLinearGradient: any;
74
+ export declare function _createPattern(ctx: CanvasRenderingContext2D, width: number, height: number, shapes: PatternShape[], background?: string): CanvasPattern | null;
75
+ /** Create pattern and memoize result to fix reactivity */
76
+ export declare const createPattern: any;
77
+ export {};
@@ -1,11 +1,25 @@
1
+ import memoize from 'memoize';
1
2
  import { cls } from '@layerstack/tailwind';
2
- import { memoize } from 'lodash-es';
3
3
  export const DEFAULT_FILL = 'rgb(0, 0, 0)';
4
4
  const CANVAS_STYLES_ELEMENT_ID = '__layerchart_canvas_styles_id';
5
+ const supportedStyles = [
6
+ 'fill',
7
+ 'fillOpacity',
8
+ 'stroke',
9
+ 'strokeWidth',
10
+ 'opacity',
11
+ 'fontWeight',
12
+ 'fontSize',
13
+ 'fontFamily',
14
+ 'textAnchor',
15
+ 'textAlign',
16
+ 'paintOrder',
17
+ ];
5
18
  /**
6
19
  * Appends or reuses `<svg>` element below `<canvas>` to resolve CSS variables and classes (ex. `stroke: var(--color-primary)` => `stroke: rgb(...)` )
7
20
  */
8
- export function getComputedStyles(canvas, { styles, classes } = {}) {
21
+ export function _getComputedStyles(canvas, { styles, classes } = {}) {
22
+ // console.count(`getComputedStyles: ${getComputedStylesKey(canvas, { styles, classes })}`);
9
23
  try {
10
24
  // Get or create `<svg>` below `<canvas>`
11
25
  let svg = document.getElementById(CANVAS_STYLES_ELEMENT_ID);
@@ -32,7 +46,11 @@ export function getComputedStyles(canvas, { styles, classes } = {}) {
32
46
  .filter((s) => !s.startsWith('transition-'))
33
47
  .join(' '));
34
48
  }
35
- const computedStyles = window.getComputedStyle(svg);
49
+ // Capture copy to enable memoization and avoid capturing all styles (which is very slow)
50
+ const computedStyles = supportedStyles.reduce((acc, style) => {
51
+ acc[style] = window.getComputedStyle(svg)[style];
52
+ return acc;
53
+ }, {});
36
54
  return computedStyles;
37
55
  }
38
56
  catch (e) {
@@ -40,38 +58,71 @@ export function getComputedStyles(canvas, { styles, classes } = {}) {
40
58
  return {};
41
59
  }
42
60
  }
61
+ function getComputedStylesKey(canvas, { styles, classes } = {}) {
62
+ return JSON.stringify({ canvasId: canvas.id, styles, classes });
63
+ }
64
+ export const getComputedStyles = memoize(_getComputedStyles, {
65
+ cacheKey: ([canvas, styleOptions]) => {
66
+ return getComputedStylesKey(canvas, styleOptions);
67
+ },
68
+ });
43
69
  /** Render onto canvas context. Supports CSS variables and classes by tranferring to hidden `<svg>` element before retrieval) */
44
- function render(ctx, render, styleOptions = {}) {
70
+ function render(ctx, render, styleOptions = {}, { applyText, } = {}) {
45
71
  // console.count('render');
46
72
  // TODO: Consider memoizing? How about reactiving to CSS variable changes (light/dark mode toggle)
47
- const computedStyles = getComputedStyles(ctx.canvas, styleOptions);
48
- // Adhere to CSS paint order: https://developer.mozilla.org/en-US/docs/Web/CSS/paint-order
49
- const paintOrder = computedStyles?.paintOrder === 'stroke' ? ['stroke', 'fill'] : ['fill', 'stroke'];
50
- if (computedStyles?.opacity) {
51
- ctx.globalAlpha = Number(computedStyles?.opacity);
73
+ let resolvedStyles;
74
+ if (styleOptions.classes == null &&
75
+ !Object.values(styleOptions.styles ?? {}).some((v) => typeof v === 'string' && v.includes('var('))) {
76
+ // Skip resolving styles if no classes are provided and no styles are using CSS variables
77
+ resolvedStyles = styleOptions.styles ?? {};
52
78
  }
53
- // Text properties
54
- ctx.font = `${computedStyles.fontWeight} ${computedStyles.fontSize} ${computedStyles.fontFamily}`; // build string instead of using `computedStyles.font` to fix/workaround `tabular-nums` returning `null`
55
- // TODO: Hack to handle `textAnchor` with canvas. Try to find a better approach
56
- if (computedStyles.textAnchor === 'middle') {
57
- ctx.textAlign = 'center';
79
+ else {
80
+ // Remove constant non-css variable properties (ex. `strokeWidth: 0.5`, `fill: #123456`) as not needed and improves memoization cache hit
81
+ const { constantStyles, variableStyles } = Object.entries(styleOptions.styles ?? {}).reduce((acc, [key, value]) => {
82
+ if (typeof value === 'number' || (typeof value === 'string' && !value.includes('var('))) {
83
+ acc.constantStyles[key] = value;
84
+ }
85
+ else if (typeof value === 'string' && value.includes('var(')) {
86
+ acc.variableStyles[key] = value;
87
+ }
88
+ return acc;
89
+ }, { constantStyles: {}, variableStyles: {} });
90
+ const computedStyles = getComputedStyles(ctx.canvas, {
91
+ styles: variableStyles,
92
+ classes: styleOptions.classes,
93
+ });
94
+ resolvedStyles = { ...computedStyles, ...constantStyles };
58
95
  }
59
- else if (computedStyles.textAnchor === 'end') {
60
- ctx.textAlign = 'right';
96
+ // Adhere to CSS paint order: https://developer.mozilla.org/en-US/docs/Web/CSS/paint-order
97
+ const paintOrder = resolvedStyles?.paintOrder === 'stroke' ? ['stroke', 'fill'] : ['fill', 'stroke'];
98
+ if (resolvedStyles?.opacity) {
99
+ ctx.globalAlpha = Number(resolvedStyles?.opacity);
61
100
  }
62
- else {
63
- ctx.textAlign = computedStyles.textAlign; // TODO: Handle/map `justify` and `match-parent`?
101
+ // font/text properties can be expensive to set (not sure why), so only apply if needed (renderText())
102
+ if (applyText) {
103
+ // Text properties
104
+ ctx.font = `${resolvedStyles.fontWeight} ${resolvedStyles.fontSize} ${resolvedStyles.fontFamily}`; // build string instead of using `computedStyles.font` to fix/workaround `tabular-nums` returning `null`
105
+ // TODO: Hack to handle `textAnchor` with canvas. Try to find a better approach
106
+ if (resolvedStyles.textAnchor === 'middle') {
107
+ ctx.textAlign = 'center';
108
+ }
109
+ else if (resolvedStyles.textAnchor === 'end') {
110
+ ctx.textAlign = 'right';
111
+ }
112
+ else {
113
+ ctx.textAlign = resolvedStyles.textAlign; // TODO: Handle/map `justify` and `match-parent`?
114
+ }
115
+ // TODO: Handle `textBaseline` / `verticalAnchor` (Text)
116
+ // ctx.textBaseline = 'top';
117
+ // ctx.textBaseline = 'middle';
118
+ // ctx.textBaseline = 'bottom';
119
+ // ctx.textBaseline = 'alphabetic';
120
+ // ctx.textBaseline = 'hanging';
121
+ // ctx.textBaseline = 'ideographic';
64
122
  }
65
- // TODO: Handle `textBaseline` / `verticalAnchor` (Text)
66
- // ctx.textBaseline = 'top';
67
- // ctx.textBaseline = 'middle';
68
- // ctx.textBaseline = 'bottom';
69
- // ctx.textBaseline = 'alphabetic';
70
- // ctx.textBaseline = 'hanging';
71
- // ctx.textBaseline = 'ideographic';
72
123
  // Dashed lines
73
- if (computedStyles.strokeDasharray.includes(',')) {
74
- const dashArray = computedStyles.strokeDasharray
124
+ if (resolvedStyles.strokeDasharray?.includes(',')) {
125
+ const dashArray = resolvedStyles.strokeDasharray
75
126
  .split(',')
76
127
  .map((s) => Number(s.replace('px', '')));
77
128
  ctx.setLineDash(dashArray);
@@ -83,11 +134,11 @@ function render(ctx, render, styleOptions = {}) {
83
134
  styleOptions.styles?.fill instanceof CanvasPattern ||
84
135
  !styleOptions.styles?.fill?.includes('var'))
85
136
  ? styleOptions.styles.fill
86
- : computedStyles?.fill;
137
+ : resolvedStyles?.fill;
87
138
  if (fill && !['none', DEFAULT_FILL].includes(fill)) {
88
139
  const currentGlobalAlpha = ctx.globalAlpha;
89
- const fillOpacity = Number(computedStyles?.fillOpacity);
90
- const opacity = Number(computedStyles?.opacity);
140
+ const fillOpacity = Number(resolvedStyles?.fillOpacity);
141
+ const opacity = Number(resolvedStyles?.opacity);
91
142
  ctx.globalAlpha = fillOpacity * opacity;
92
143
  ctx.fillStyle = fill;
93
144
  render.fill(ctx);
@@ -100,12 +151,12 @@ function render(ctx, render, styleOptions = {}) {
100
151
  (styleOptions.styles?.stroke instanceof CanvasGradient ||
101
152
  !styleOptions.styles?.stroke?.includes('var'))
102
153
  ? styleOptions.styles?.stroke
103
- : computedStyles?.stroke;
154
+ : resolvedStyles?.stroke;
104
155
  if (stroke && !['none'].includes(stroke)) {
105
156
  ctx.lineWidth =
106
- typeof computedStyles?.strokeWidth === 'string'
107
- ? Number(computedStyles?.strokeWidth?.replace('px', ''))
108
- : (computedStyles?.strokeWidth ?? 1);
157
+ typeof resolvedStyles?.strokeWidth === 'string'
158
+ ? Number(resolvedStyles?.strokeWidth?.replace('px', ''))
159
+ : (resolvedStyles?.strokeWidth ?? 1);
109
160
  ctx.strokeStyle = stroke;
110
161
  render.stroke(ctx);
111
162
  }
@@ -125,7 +176,7 @@ export function renderText(ctx, text, coords, styleOptions = {}) {
125
176
  render(ctx, {
126
177
  fill: (ctx) => ctx.fillText(text.toString(), coords.x, coords.y),
127
178
  stroke: (ctx) => ctx.strokeText(text.toString(), coords.x, coords.y),
128
- }, styleOptions);
179
+ }, styleOptions, { applyText: true });
129
180
  }
130
181
  }
131
182
  export function renderRect(ctx, coords, styleOptions = {}) {
@@ -194,9 +245,8 @@ export function _createLinearGradient(ctx, x0, y0, x1, y1, stops) {
194
245
  return gradient;
195
246
  }
196
247
  /** Create linear gradient and memoize result to fix reactivity */
197
- export const createLinearGradient = memoize(_createLinearGradient, (ctx, x0, y0, x1, y1, stops) => {
198
- const key = JSON.stringify({ x0, y0, x1, y1, stops });
199
- return key;
248
+ export const createLinearGradient = memoize(_createLinearGradient, {
249
+ cacheKey: (args) => JSON.stringify(args.slice(1)), // Ignore `ctx` argument
200
250
  });
201
251
  export function _createPattern(ctx, width, height, shapes, background) {
202
252
  const patternCanvas = document.createElement('canvas');
@@ -229,7 +279,6 @@ export function _createPattern(ctx, width, height, shapes, background) {
229
279
  return pattern;
230
280
  }
231
281
  /** Create pattern and memoize result to fix reactivity */
232
- export const createPattern = memoize(_createPattern, (ctx, width, height, shapes, background) => {
233
- const key = JSON.stringify({ width, height, shapes, background });
234
- return key;
282
+ export const createPattern = memoize(_createPattern, {
283
+ cacheKey: (args) => JSON.stringify(args.slice(1)), // Ignore `ctx` argument
235
284
  });
@@ -0,0 +1,49 @@
1
+ export declare const getStringWidth: any;
2
+ export type RasterizeTextOptions = {
3
+ fontSize?: string;
4
+ fontWeight?: number;
5
+ fontFamily?: string;
6
+ textAlign?: CanvasTextAlign;
7
+ textBaseline?: CanvasTextBaseline;
8
+ spacing?: number;
9
+ width?: number;
10
+ height?: number;
11
+ x?: number;
12
+ y?: number;
13
+ };
14
+ export declare function rasterizeText(text: string, options?: RasterizeTextOptions): number[][];
15
+ export declare function toTitleCase(str: string): string;
16
+ export type TruncateTextOptions = {
17
+ /**
18
+ * The maximum pixel width (optional if maxChars is provided).
19
+ */
20
+ maxWidth?: number;
21
+ /**
22
+ * CSS style for width calculation
23
+ */
24
+ style?: CSSStyleDeclaration;
25
+ /**
26
+ * The maximum character count
27
+ */
28
+ maxChars?: number;
29
+ /**
30
+ * Where to place the ellipsis: 'start', 'middle', or 'end'
31
+ *
32
+ * @default 'end'
33
+ */
34
+ position?: 'start' | 'middle' | 'end';
35
+ /**
36
+ * The character(s) to use as the ellipsis
37
+ *
38
+ * @default '…'
39
+ */
40
+ ellipsis?: string;
41
+ };
42
+ /**
43
+ * Truncates a string to fit within a specified pixel width or character count.
44
+ * If the string's width exceeds the maxWidth, it will be truncated. If the character
45
+ * count exceeds maxChars, it will also be truncated.
46
+ *
47
+ * The ellipsis can be placed at the start, middle, or end of the string.
48
+ */
49
+ export declare function truncateText(text: string, { position, ellipsis, maxWidth, style, maxChars }: TruncateTextOptions): string;
@@ -1,4 +1,4 @@
1
- import { memoize } from 'lodash-es';
1
+ import memoize from 'memoize';
2
2
  const MEASUREMENT_ELEMENT_ID = '__text_measurement_id';
3
3
  function _getStringWidth(str, style) {
4
4
  try {
@@ -24,7 +24,9 @@ function _getStringWidth(str, style) {
24
24
  return null;
25
25
  }
26
26
  }
27
- export const getStringWidth = memoize(_getStringWidth, (str, style) => `${str}_${JSON.stringify(style)}`);
27
+ export const getStringWidth = memoize(_getStringWidth, {
28
+ cacheKey: ([str, style]) => `${str}_${JSON.stringify(style)}`,
29
+ });
28
30
  export function rasterizeText(text, options = {}) {
29
31
  const fontSize = options.fontSize ?? '200px';
30
32
  const fontWeight = options.fontWeight ?? 600;
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "author": "Sean Lynch <techniq35@gmail.com>",
5
5
  "license": "MIT",
6
6
  "repository": "techniq/layerchart",
7
- "version": "2.0.0-next.19",
7
+ "version": "2.0.0-next.20",
8
8
  "devDependencies": {
9
9
  "@changesets/cli": "^2.29.4",
10
10
  "@iconify-json/lucide": "^1.2.44",
@@ -97,6 +97,7 @@
97
97
  "d3-tile": "^1.0.0",
98
98
  "d3-time": "^3.1.0",
99
99
  "lodash-es": "^4.17.21",
100
+ "memoize": "^10.1.0",
100
101
  "runed": "^0.28.0"
101
102
  },
102
103
  "peerDependencies": {
@@ -1,8 +0,0 @@
1
- import { type TimeInterval } from 'd3-time';
2
- /**
3
- * Get the date at the end of the interval
4
- * Similar to `interval.ceil(date)` except:
5
- * - returns end of day instead of start of next day
6
- * - properly handles start of day (i.e. not return same date)
7
- */
8
- export declare function endOfInterval(date: Date, interval: TimeInterval): Date;
@@ -1,11 +0,0 @@
1
- import {} from 'd3-time';
2
- /**
3
- * Get the date at the end of the interval
4
- * Similar to `interval.ceil(date)` except:
5
- * - returns end of day instead of start of next day
6
- * - properly handles start of day (i.e. not return same date)
7
- */
8
- export function endOfInterval(date, interval) {
9
- // Can not use `new Date(+interval.ceil(date) - 1)`; as `.ceil()` will return same date when start of the day (matching `.floor()`)
10
- return new Date(interval.offset(interval.floor(date), 1).getTime() - 1);
11
- }
@@ -1,2 +0,0 @@
1
- import { memoize } from 'lodash-es';
2
- export const memoizeObject = memoize((obj) => obj, (obj) => JSON.stringify(obj));