@bicharts/chart-host 0.6.61 → 0.6.62

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.
@@ -3830,6 +3830,47 @@ function applyLabelContrast(container, opts = {}) {
3830
3830
  }
3831
3831
  }
3832
3832
 
3833
+ // src/idScope.ts
3834
+ var seq = 0;
3835
+ function newIdScope() {
3836
+ seq += 1;
3837
+ return "h" + seq;
3838
+ }
3839
+ var URL_REF = /url\(\s*(['"]?)#([^'")\s]+)\1\s*\)/g;
3840
+ function scopeChartIds(container, scope) {
3841
+ const report = { renamed: 0, collisions: [] };
3842
+ try {
3843
+ const tag = "--" + scope, doc = container.ownerDocument, map = /* @__PURE__ */ new Map();
3844
+ const refs = [], used = /* @__PURE__ */ new Set();
3845
+ container.querySelectorAll("*").forEach((el) => {
3846
+ for (const a3 of el.getAttributeNames()) {
3847
+ const v = el.getAttribute(a3);
3848
+ const href = (a3 === "href" || a3 === "xlink:href") && v[0] === "#";
3849
+ if (!href && v.indexOf("url(") < 0) continue;
3850
+ refs.push([el, a3, v]);
3851
+ if (href) used.add(v.slice(1));
3852
+ for (const m4 of v.matchAll(URL_REF)) used.add(m4[2]);
3853
+ }
3854
+ });
3855
+ container.querySelectorAll("[id]").forEach((el) => {
3856
+ const id = el.id;
3857
+ if (!used.has(id) || id.endsWith(tag) || id.startsWith("bic-")) return;
3858
+ const q2 = id.replace(/["\\]/g, "\\$&");
3859
+ if (doc && Array.from(doc.querySelectorAll(`[id="${q2}"],[id^="${q2}--h"]`)).some((o4) => !container.contains(o4))) {
3860
+ report.collisions.push(id);
3861
+ }
3862
+ map.set(id, el.id = id + tag);
3863
+ report.renamed++;
3864
+ });
3865
+ for (const [el, a3, v] of refs) {
3866
+ const nv = v[0] === "#" && map.has(v.slice(1)) ? "#" + map.get(v.slice(1)) : v.replace(URL_REF, (m4, q2, id) => map.has(id) ? `url(${q2}#${map.get(id)}${q2})` : m4);
3867
+ if (nv !== v) el.setAttribute(a3, nv);
3868
+ }
3869
+ } catch {
3870
+ }
3871
+ return report;
3872
+ }
3873
+
3833
3874
  // src/invalidSentinel.ts
3834
3875
  function isInvalidSentinelError(text2) {
3835
3876
  if (!text2) return false;
@@ -4136,6 +4177,8 @@ function createChartHost(container, config) {
4136
4177
  let destroyed = false;
4137
4178
  let warnedGeo = false;
4138
4179
  let warnedBlank = false;
4180
+ let warnedIds = false;
4181
+ const idScope = newIdScope();
4139
4182
  let renderSeq = 0;
4140
4183
  const annotationLayer = createAnnotationLayer(container, (a3) => config.onAnnotationClick?.(a3));
4141
4184
  const drawAnnotations = () => annotationLayer.draw(
@@ -4307,6 +4350,20 @@ function createChartHost(container, config) {
4307
4350
  }
4308
4351
  };
4309
4352
  const runPostRenderPasses = () => {
4353
+ try {
4354
+ const r2 = scopeChartIds(container, idScope);
4355
+ if (r2.collisions.length && !warnedIds) {
4356
+ warnedIds = true;
4357
+ try {
4358
+ (win?.console ?? console)?.warn(
4359
+ `[@bicharts/chart-host] this chart named ${r2.collisions.length} id(s) another element on the page already uses (${r2.collisions.slice(0, 5).join(", ")}). They were scoped to this chart, so its clip paths and gradients resolve to its own definitions.`
4360
+ );
4361
+ } catch {
4362
+ }
4363
+ }
4364
+ config.onIdScope?.(r2);
4365
+ } catch {
4366
+ }
4310
4367
  if (labelContrastOpts !== false) {
4311
4368
  try {
4312
4369
  const lc = labelContrastOpts === true ? {} : { ...labelContrastOpts };
@@ -4386,7 +4443,7 @@ function createChartHost(container, config) {
4386
4443
  const host = {
4387
4444
  render() {
4388
4445
  if (destroyed) return;
4389
- const seq = ++renderSeq;
4446
+ const seq2 = ++renderSeq;
4390
4447
  stopAnim();
4391
4448
  container.innerHTML = "";
4392
4449
  if (!renderFn) {
@@ -4439,7 +4496,7 @@ function createChartHost(container, config) {
4439
4496
  };
4440
4497
  Promise.resolve(outcome).then(
4441
4498
  () => done(() => {
4442
- if (destroyed || seq !== renderSeq) {
4499
+ if (destroyed || seq2 !== renderSeq) {
4443
4500
  resolve();
4444
4501
  return;
4445
4502
  }
@@ -4447,7 +4504,7 @@ function createChartHost(container, config) {
4447
4504
  resolve();
4448
4505
  }),
4449
4506
  (err) => done(() => {
4450
- if (destroyed || seq !== renderSeq) {
4507
+ if (destroyed || seq2 !== renderSeq) {
4451
4508
  resolve();
4452
4509
  return;
4453
4510
  }
@@ -4773,6 +4830,8 @@ export {
4773
4830
  LABEL_CONTRAST_CAP,
4774
4831
  LABEL_PILL_CLASS,
4775
4832
  applyLabelContrast,
4833
+ newIdScope,
4834
+ scopeChartIds,
4776
4835
  isInvalidSentinelError,
4777
4836
  invalidSentinelReason,
4778
4837
  ANNOTATION_LAYER_CLASS,
package/dist/index.mjs CHANGED
@@ -91,6 +91,7 @@ import {
91
91
  measureContainerBoxes,
92
92
  minContrastOver,
93
93
  needsScroll,
94
+ newIdScope,
94
95
  nextSelection,
95
96
  noopViewStateProvider,
96
97
  o,
@@ -108,6 +109,7 @@ import {
108
109
  resolveAutoColorScale,
109
110
  resolveOptions,
110
111
  rowIdxsFromMark,
112
+ scopeChartIds,
111
113
  scrollFitFor,
112
114
  sessionViewStateProvider,
113
115
  straddleSampleGrid,
@@ -120,7 +122,7 @@ import {
120
122
  toSourceRows,
121
123
  unpinScrolledAxis,
122
124
  valuePlacementFlag
123
- } from "./chunk-Q6AGV652.mjs";
125
+ } from "./chunk-ZLJM3EIP.mjs";
124
126
  import "./chunk-A2GMXZP7.mjs";
125
127
  import {
126
128
  ACTIVE_TICK_CLASS,
@@ -3008,6 +3010,7 @@ export {
3008
3010
  minContrastOver,
3009
3011
  mountProductFeedbackBox,
3010
3012
  needsScroll,
3013
+ newIdScope,
3011
3014
  newQualifyGroupState,
3012
3015
  newQualifyRefusalGroupState,
3013
3016
  nextSelection,
@@ -3058,6 +3061,7 @@ export {
3058
3061
  resolveOptions,
3059
3062
  rowIdxsFromMark,
3060
3063
  sameMarker,
3064
+ scopeChartIds,
3061
3065
  scrollFitFor,
3062
3066
  servedCorrelationProves,
3063
3067
  sessionViewStateProvider,
package/dist/react.mjs CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  loadGeo,
23
23
  syncMemberSelection,
24
24
  toSourceRows
25
- } from "./chunk-Q6AGV652.mjs";
25
+ } from "./chunk-ZLJM3EIP.mjs";
26
26
  import "./chunk-A2GMXZP7.mjs";
27
27
  import "./chunk-WNGJUPEI.mjs";
28
28
  import "./chunk-GHODWOAL.mjs";
@@ -6,6 +6,7 @@ import { type ColourSpreadCensus } from "./colourSpread";
6
6
  import { type ValuePlacementCensus } from "./valuePlacement";
7
7
  import { type FitRenderedChartOptions, type FitRenderedChartResult } from "./fitDom";
8
8
  import { type LabelContrastOptions, type LabelContrastReport } from "./labelContrastDom";
9
+ import { type IdScopeReport } from "./idScope";
9
10
  import { type MarkAnnotation, type AnnotationReport } from "./annotations";
10
11
  export type RenderFn = (container: HTMLElement, data: any, options: RenderOptions) => void;
11
12
  /**
@@ -166,6 +167,11 @@ export interface ChartHostConfig {
166
167
  labelContrast?: boolean | LabelContrastOptions;
167
168
  /** What the label-contrast pass did after each render - counts, so a host can log them. */
168
169
  onLabelContrast?: (report: LabelContrastReport) => void;
170
+ /**
171
+ * What the id-scoping pass did after each render: how many of the chart's ids got this host's suffix, and which
172
+ * of them also existed elsewhere in the page (a clash it prevented - two same-size charts naming one clip path).
173
+ */
174
+ onIdScope?: (report: IdScopeReport) => void;
169
175
  /**
170
176
  * THE CHART SAID THE DATA LACKS SOMETHING IT IS BUILT ON. Generated code has one sanctioned
171
177
  * hard stop at runtime: a column the chart needs is gone from `data.columns`, and it throws
@@ -0,0 +1,9 @@
1
+ /** A fresh suffix for one chart host. Unique within the page for the page's lifetime. */
2
+ export declare function newIdScope(): string;
3
+ export interface IdScopeReport {
4
+ /** How many ids were given the suffix. */
5
+ renamed: number;
6
+ /** Ids that also existed outside this chart when the pass ran - each one a clash this pass prevented. */
7
+ collisions: string[];
8
+ }
9
+ export declare function scopeChartIds(container: Element, scope: string): IdScopeReport;
@@ -27,6 +27,7 @@ export { formatSourceDate, formatSourceDateFor, isExcelDateFormat, type SourceFo
27
27
  export { composeSummaryText } from "./summaryText";
28
28
  export { ensureCrossfilterHitTargets, type HitTargetReport } from "./hitTargets";
29
29
  export { applyLabelContrast, LABEL_CONTRAST_DONE_ATTR, LABEL_CONTRAST_CAP, LABEL_PILL_CLASS, type LabelContrastOptions, type LabelContrastReport, } from "./labelContrastDom";
30
+ export { scopeChartIds, newIdScope, type IdScopeReport } from "./idScope";
30
31
  export { decideLabelColor, toRGBA, compositeOver, relativeLuminance, contrastRatio, isPillBackdropAlpha, pillBacksGlyph, backingHoldsGlyph, cellSuppressesNormalize, glyphSampleGrid, DARK_TEXT, LIGHT_TEXT, MIN_CONTRAST, WHITE_TEXT_BG_LUM, PILL_MIN_ALPHA, PILL_OPAQUE_ALPHA, PILL_MIN_COVERAGE, PAGE_MATCH_TOLERANCE, BACKING_MAJORITY, GLYPH_SAMPLE_N, straddles, straddleSampleGrid, minContrastOver, pickColorOver, STRADDLE_COLS, STRADDLE_ROWS, STRADDLE_MIN_SHARE, STRADDLE_TARGET_CONTRAST, STRADDLE_PILL_ALPHA, type LabelDecision, } from "./labelContrast";
31
32
  export { censusMarks, isBlankRender, blankRenderFlag, type MarkCensus, type BlankVerdictInput } from "./blankRender";
32
33
  export { isInvalidSentinelError, invalidSentinelReason } from "./invalidSentinel";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bicharts/chart-host",
3
- "version": "0.6.61",
3
+ "version": "0.6.62",
4
4
  "description": "Run a BIC-generated D3 chart in any web host: compiles the generated render() function, applies the shared option defaults, resolves mark clicks (through tooltip overlays), owns the selection affordance, and translates row indices between cross-filtered charts. The same contract the BIC Power BI visual implements, minus Power BI. React bindings at @bicharts/chart-host/react.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",