@nanahoshi/mona-ui 0.15.1 → 0.15.3

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.
@@ -23612,6 +23612,7 @@ class CartesianAxisDomainResolver {
23612
23612
  }
23613
23613
 
23614
23614
  class CartesianAxisLabelGeometry {
23615
+ static #cachedGetContext = null;
23615
23616
  static #measureCanvas = null;
23616
23617
  static #measureCtx = null;
23617
23618
  static createTickKey(axis, axisType, value, index) {
@@ -23634,11 +23635,24 @@ class CartesianAxisLabelGeometry {
23634
23635
  try {
23635
23636
  if (!CartesianAxisLabelGeometry.#measureCanvas) {
23636
23637
  CartesianAxisLabelGeometry.#measureCanvas = document.createElement("canvas");
23638
+ }
23639
+ // Cache the resolved 2D context, but invalidate it whenever
23640
+ // HTMLCanvasElement.prototype.getContext itself has changed identity (e.g. a test
23641
+ // installs/restores a vi.spyOn mock) so a swapped implementation is never frozen in
23642
+ // stale. This keeps the common case (thousands of tick-label measurements against a
23643
+ // stable getContext) a cheap reference reuse instead of re-invoking getContext (and,
23644
+ // under test mocks that fabricate a fresh context object per call, re-allocating one)
23645
+ // on every single call.
23646
+ const currentGetContext = CartesianAxisLabelGeometry.#measureCanvas.getContext;
23647
+ if (!CartesianAxisLabelGeometry.#measureCtx ||
23648
+ CartesianAxisLabelGeometry.#cachedGetContext !== currentGetContext) {
23637
23649
  CartesianAxisLabelGeometry.#measureCtx = CartesianAxisLabelGeometry.#measureCanvas.getContext("2d");
23650
+ CartesianAxisLabelGeometry.#cachedGetContext = currentGetContext;
23638
23651
  }
23639
- if (CartesianAxisLabelGeometry.#measureCtx) {
23640
- CartesianAxisLabelGeometry.#measureCtx.font = font;
23641
- const measuredWidth = CartesianAxisLabelGeometry.#measureCtx.measureText(formattedText).width;
23652
+ const ctx = CartesianAxisLabelGeometry.#measureCtx;
23653
+ if (ctx) {
23654
+ ctx.font = font;
23655
+ const measuredWidth = ctx.measureText(formattedText).width;
23642
23656
  if (typeof measuredWidth === "number" && !isNaN(measuredWidth) && measuredWidth > 0) {
23643
23657
  return {
23644
23658
  height: 16,
@@ -36039,12 +36053,27 @@ class ChartLayoutEngine {
36039
36053
  }
36040
36054
  }
36041
36055
 
36056
+ /**
36057
+ * Small overflow allowance so marks whose stroke or radius sits exactly at a
36058
+ * domain extreme (a peak touching the max, a hovered point marker at the
36059
+ * first/last category) aren't visually chopped in half by the plot clip rect.
36060
+ */
36061
+ const PLOT_CLIP_OVERFLOW = 8;
36042
36062
  function crispPixel(pixel, lineWidth = 1) {
36043
36063
  if (lineWidth % 2 === 1) {
36044
36064
  return Math.floor(pixel) + 0.5;
36045
36065
  }
36046
36066
  return Math.round(pixel);
36047
36067
  }
36068
+ /**
36069
+ * Clips the canvas context to `plotRect`, inflated on all four sides by
36070
+ * {@link PLOT_CLIP_OVERFLOW} so marks touching a domain extreme aren't cut in half.
36071
+ */
36072
+ function clipToPlotRect(context, plotRect) {
36073
+ context.beginPath();
36074
+ context.rect(plotRect.x - PLOT_CLIP_OVERFLOW, plotRect.y - PLOT_CLIP_OVERFLOW, plotRect.width + PLOT_CLIP_OVERFLOW * 2, plotRect.height + PLOT_CLIP_OVERFLOW * 2);
36075
+ context.clip();
36076
+ }
36048
36077
  function drawRoundedRectCorners(context, x, y, width, height, radii) {
36049
36078
  if (width <= 0 || height <= 0) {
36050
36079
  return;
@@ -36873,9 +36902,7 @@ class CartesianOverlayRenderer {
36873
36902
  return;
36874
36903
  }
36875
36904
  context.save();
36876
- context.beginPath();
36877
- context.rect(plotRect.x, plotRect.y, plotRect.width, plotRect.height);
36878
- context.clip();
36905
+ clipToPlotRect(context, plotRect);
36879
36906
  // 1. Overlay Bands
36880
36907
  for (const band of overlayScene.referenceBands) {
36881
36908
  if (band.layer === "overlay") {
@@ -36900,9 +36927,7 @@ class CartesianOverlayRenderer {
36900
36927
  return;
36901
36928
  }
36902
36929
  context.save();
36903
- context.beginPath();
36904
- context.rect(plotRect.x, plotRect.y, plotRect.width, plotRect.height);
36905
- context.clip();
36930
+ clipToPlotRect(context, plotRect);
36906
36931
  // 1. Underlay Bands
36907
36932
  for (const band of overlayScene.referenceBands) {
36908
36933
  if (band.layer === "underlay") {
@@ -36947,9 +36972,7 @@ class CartesianCrosshairRenderer {
36947
36972
  }
36948
36973
  const dash = getCrosshairDash$1(registration.lineStyle());
36949
36974
  context.save();
36950
- context.beginPath();
36951
- context.rect(plotRect.x, plotRect.y, plotRect.width, plotRect.height);
36952
- context.clip();
36975
+ clipToPlotRect(context, plotRect);
36953
36976
  context.strokeStyle = style.color;
36954
36977
  context.lineWidth = style.width;
36955
36978
  context.globalAlpha = style.opacity;
@@ -36999,9 +37022,7 @@ class CartesianInteractionOverlayRenderer {
36999
37022
  return;
37000
37023
  }
37001
37024
  context.save();
37002
- context.beginPath();
37003
- context.rect(plotRect.x, plotRect.y, plotRect.width, plotRect.height);
37004
- context.clip();
37025
+ clipToPlotRect(context, plotRect);
37005
37026
  const markerStrokeColor = styleResolver.resolveCssVariable("--mona-chart-marker-stroke-color") ||
37006
37027
  styleResolver.resolveCssVariable("--color-surface") ||
37007
37028
  "#ffffff";
@@ -37141,9 +37162,7 @@ class CartesianSelectionOverlayRenderer {
37141
37162
  }
37142
37163
  const { color = "#3b82f6", fillOpacity = 0.12, plotRect, strokeWidth = 2 } = options;
37143
37164
  ctx.save();
37144
- ctx.beginPath();
37145
- ctx.rect(plotRect.x, plotRect.y, plotRect.width, plotRect.height);
37146
- ctx.clip();
37165
+ clipToPlotRect(ctx, plotRect);
37147
37166
  ctx.strokeStyle = color;
37148
37167
  ctx.lineWidth = strokeWidth;
37149
37168
  for (const hit of scene.hits) {
@@ -37468,9 +37487,7 @@ class CartesianChartRenderer {
37468
37487
  static renderSeriesLayer(context, scene) {
37469
37488
  const { plotRect, series } = scene;
37470
37489
  context.save();
37471
- context.beginPath();
37472
- context.rect(plotRect.x, plotRect.y, plotRect.width, plotRect.height);
37473
- context.clip();
37490
+ clipToPlotRect(context, plotRect);
37474
37491
  for (const s of series) {
37475
37492
  switch (s.type) {
37476
37493
  case "area":
@@ -41274,6 +41291,12 @@ class SvgRangeBarSeriesRenderer {
41274
41291
  }
41275
41292
  }
41276
41293
 
41294
+ /**
41295
+ * Small overflow allowance so marks whose stroke or radius sits exactly at a
41296
+ * domain extreme (a peak touching the max, a point at the first/last category)
41297
+ * aren't visually chopped in half by the plot clip rectangle.
41298
+ */
41299
+ const SERIES_CLIP_OVERFLOW$1 = 8;
41277
41300
  class SvgCartesianChartRenderer {
41278
41301
  #axisRenderer;
41279
41302
  #brushRenderer;
@@ -41468,7 +41491,7 @@ class SvgCartesianChartRenderer {
41468
41491
  return;
41469
41492
  }
41470
41493
  this.#clearCrossfadeScopes();
41471
- const plotClipUrl = defs.useClipRect("plot-clip", plotRect.x, plotRect.y, plotRect.width, plotRect.height);
41494
+ const plotClipUrl = defs.useClipRect("plot-clip", plotRect.x - SERIES_CLIP_OVERFLOW$1, plotRect.y - SERIES_CLIP_OVERFLOW$1, plotRect.width + SERIES_CLIP_OVERFLOW$1 * 2, plotRect.height + SERIES_CLIP_OVERFLOW$1 * 2);
41472
41495
  // 1. Grid
41473
41496
  this.#gridRenderer.render(scene, styleResolver);
41474
41497
  // 2. Static Underlays
@@ -41527,7 +41550,7 @@ class SvgCartesianChartRenderer {
41527
41550
  const fromOpacity = Math.max(0, Math.min(1, 1 - p));
41528
41551
  const toOpacity = Math.max(0, Math.min(1, p));
41529
41552
  const toDefs = defs.withScope("cf-to");
41530
- const toPlotClipUrl = toDefs.useClipRect("plot-clip", toScene.plotRect.x, toScene.plotRect.y, toScene.plotRect.width, toScene.plotRect.height);
41553
+ const toPlotClipUrl = toDefs.useClipRect("plot-clip", toScene.plotRect.x - SERIES_CLIP_OVERFLOW$1, toScene.plotRect.y - SERIES_CLIP_OVERFLOW$1, toScene.plotRect.width + SERIES_CLIP_OVERFLOW$1 * 2, toScene.plotRect.height + SERIES_CLIP_OVERFLOW$1 * 2);
41531
41554
  // Persistent selection and data labels are suppressed during animation
41532
41555
  this.#selectionRenderer.clear();
41533
41556
  this.#dataLabelRenderer.clear();
@@ -41572,7 +41595,7 @@ class SvgCartesianChartRenderer {
41572
41595
  this.#layers.series.removeAttribute("clip-path");
41573
41596
  if (fromScene) {
41574
41597
  const fromDefs = defs.withScope("cf-from");
41575
- const fromPlotClipUrl = fromDefs.useClipRect("plot-clip", fromScene.plotRect.x, fromScene.plotRect.y, fromScene.plotRect.width, fromScene.plotRect.height);
41598
+ const fromPlotClipUrl = fromDefs.useClipRect("plot-clip", fromScene.plotRect.x - SERIES_CLIP_OVERFLOW$1, fromScene.plotRect.y - SERIES_CLIP_OVERFLOW$1, fromScene.plotRect.width + SERIES_CLIP_OVERFLOW$1 * 2, fromScene.plotRect.height + SERIES_CLIP_OVERFLOW$1 * 2);
41576
41599
  if (!this.#crossfadeFromScope) {
41577
41600
  this.#crossfadeFromScope = createSvgElement("g");
41578
41601
  this.#crossfadeFromScope.setAttribute("data-crossfade-scope", "from");
@@ -41641,6 +41664,12 @@ class SvgCartesianChartRenderer {
41641
41664
  }
41642
41665
  }
41643
41666
 
41667
+ /**
41668
+ * Small overflow allowance so marks whose stroke or radius sits exactly at a
41669
+ * domain extreme (a peak touching the max, a point at the first/last category)
41670
+ * aren't visually chopped in half by the plot clip rectangle.
41671
+ */
41672
+ const SERIES_CLIP_OVERFLOW = 8;
41644
41673
  class SvgCartesianContentRenderer {
41645
41674
  #axesGroup;
41646
41675
  #axisRenderer;
@@ -41778,7 +41807,7 @@ class SvgCartesianContentRenderer {
41778
41807
  this.clear();
41779
41808
  return;
41780
41809
  }
41781
- const plotClipUrl = defs.useClipRect("plot-clip", plotRect.x, plotRect.y, plotRect.width, plotRect.height);
41810
+ const plotClipUrl = defs.useClipRect("plot-clip", plotRect.x - SERIES_CLIP_OVERFLOW, plotRect.y - SERIES_CLIP_OVERFLOW, plotRect.width + SERIES_CLIP_OVERFLOW * 2, plotRect.height + SERIES_CLIP_OVERFLOW * 2);
41782
41811
  // 1. Grid
41783
41812
  this.#gridRenderer.render(scene, styleResolver);
41784
41813
  // 2. Underlays
@@ -146,6 +146,7 @@ class ListService {
146
146
  enabled: false,
147
147
  firstLast: false,
148
148
  showInfo: true,
149
+ pageSize: 10,
149
150
  pageSizeValues: true,
150
151
  previousNext: false,
151
152
  type: "numeric",
@@ -1,6 +1,6 @@
1
1
  import { NgTemplateOutlet } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { Directive, inject, DestroyRef, ElementRef, computed, contentChild, TemplateRef, input, output, effect, untracked, afterRenderEffect, Optional, SkipSelf, Component, afterNextRender } from '@angular/core';
3
+ import { Directive, inject, DestroyRef, ElementRef, computed, contentChild, TemplateRef, input, output, effect, untracked, afterRenderEffect, Optional, SkipSelf, Component, model, afterNextRender } from '@angular/core';
4
4
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
5
5
  import { ListService, ListComponent, ListHeaderTemplateDirective, ListFooterTemplateDirective, ListGroupHeaderTemplateDirective, ListItemTemplateDirective, ListNoDataTemplateDirective } from '@nanahoshi/mona-ui/internal/list';
6
6
  import { PagerComponent } from '@nanahoshi/mona-ui/pager';
@@ -390,35 +390,54 @@ class ListViewPageableDirective {
390
390
  firstLast: false,
391
391
  type: "numeric",
392
392
  previousNext: true,
393
+ pageSize: 10,
393
394
  pageSizeValues: [5, 10, 20, 25, 50, 100],
394
395
  visiblePages: 5
395
396
  };
396
397
  this.#listService = inject(ListService);
397
398
  this.options = input("", { ...(ngDevMode ? { debugName: "options" } : /* istanbul ignore next */ {}), alias: "monaListViewPageable" });
399
+ /**
400
+ * @description The current page. Supports two-way binding via `[(page)]`; updates when the user navigates the pager and can be set to navigate programmatically.
401
+ * @default 1
402
+ */
403
+ this.page = model(1, /* @ts-ignore */
404
+ ...(ngDevMode ? [{ debugName: "page" }] : /* istanbul ignore next */ []));
398
405
  effect(() => {
399
406
  const options = this.options();
400
407
  untracked(() => {
401
- if (options === "") {
402
- this.#listService.setPageableOptions(this.#defaultOptions);
408
+ const mergedOptions = options === "" ? this.#defaultOptions : { ...this.#defaultOptions, ...options };
409
+ this.#listService.setPageableOptions(mergedOptions);
410
+ this.#listService.pageState.update(s => ({ ...s, take: mergedOptions.pageSize }));
411
+ });
412
+ });
413
+ effect(() => {
414
+ const page = this.page();
415
+ untracked(() => {
416
+ const pageState = this.#listService.pageState();
417
+ if (pageState.page === page) {
418
+ return;
403
419
  }
404
- else {
405
- this.#listService.setPageableOptions({
406
- ...this.#defaultOptions,
407
- ...options
408
- });
420
+ this.#listService.pageState.set({ page, skip: (page - 1) * pageState.take, take: pageState.take });
421
+ });
422
+ });
423
+ effect(() => {
424
+ const pageState = this.#listService.pageState();
425
+ untracked(() => {
426
+ if (this.page() !== pageState.page) {
427
+ this.page.set(pageState.page);
409
428
  }
410
429
  });
411
430
  });
412
431
  }
413
432
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ListViewPageableDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
414
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: ListViewPageableDirective, isStandalone: true, selector: "mona-list-view[monaListViewPageable]", inputs: { options: { classPropertyName: "options", publicName: "monaListViewPageable", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
433
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: ListViewPageableDirective, isStandalone: true, selector: "mona-list-view[monaListViewPageable]", inputs: { options: { classPropertyName: "options", publicName: "monaListViewPageable", isSignal: true, isRequired: false, transformFunction: null }, page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { page: "pageChange" }, ngImport: i0 }); }
415
434
  }
416
435
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ListViewPageableDirective, decorators: [{
417
436
  type: Directive,
418
437
  args: [{
419
438
  selector: "mona-list-view[monaListViewPageable]"
420
439
  }]
421
- }], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "monaListViewPageable", required: false }] }] } });
440
+ }], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "monaListViewPageable", required: false }] }], page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: false }] }, { type: i0.Output, args: ["pageChange"] }] } });
422
441
 
423
442
  class ListViewSelectableDirective {
424
443
  #defaultOptions;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanahoshi/mona-ui",
3
- "version": "0.15.1",
3
+ "version": "0.15.3",
4
4
  "description": "A very light Angular UI framework",
5
5
  "author": "Nanahoshi",
6
6
  "repository": {