@silverpoint/core 0.1.0 → 0.2.0

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/index.js CHANGED
@@ -244,6 +244,21 @@ var WARNINGS = {
244
244
  what: "the path byte budget of 40 KB is exceeded",
245
245
  todo: "Use `hatchFill: 'tile'` or a larger hatch gap"
246
246
  },
247
+ SP014: {
248
+ req: "REQ-204",
249
+ what: "a dashboard cell spans more columns than the breakpoint has; it is clamped",
250
+ todo: "Lower the span, or give that breakpoint more columns"
251
+ },
252
+ SP015: {
253
+ req: "REQ-205",
254
+ what: "the dashboard layout and its children disagree; unmatched children are placed in source order, span 1",
255
+ todo: "Give every child a `cell` the layout declares, once"
256
+ },
257
+ SP016: {
258
+ req: "REQ-217",
259
+ what: "a chart in a linked dashboard has no field named by `link.key`; it shows no linked mark",
260
+ todo: "Link on a field every linked chart carries, or leave this chart out of the link"
261
+ },
247
262
  SP013: {
248
263
  req: "REQ-032",
249
264
  what: "the @silverpoint/fonts face failed to load; rendering fell back to the system stack",
@@ -285,6 +300,7 @@ function pathView(stroke) {
285
300
  role: stroke.role,
286
301
  paint,
287
302
  dash: stroke.dash ?? null,
303
+ weight: stroke.tonalWeight === void 0 ? null : String(stroke.tonalWeight),
288
304
  fill: paint === "tile" && stroke.tile ? `url(#${stroke.tile})` : null
289
305
  };
290
306
  }
@@ -336,6 +352,7 @@ function pathString(path) {
336
352
  ["data-role", path.role],
337
353
  ["data-paint", path.paint],
338
354
  ["data-dash", path.dash],
355
+ ["data-weight", path.weight],
339
356
  ["fill", path.fill]
340
357
  ])}></path>`;
341
358
  }
@@ -4204,7 +4221,6 @@ function fits(frame, name, at2, inner, outer, start, end) {
4204
4221
  });
4205
4222
  }
4206
4223
  function buildVolvelleChart(props, context) {
4207
- const usesDemo = props.data === void 0;
4208
4224
  const all = props.data ?? VOLVELLE_CHART_DEMO;
4209
4225
  const { locale } = context;
4210
4226
  const cap = props.rings !== void 0 && Number.isInteger(props.rings) && props.rings > 0 ? props.rings : all.length;
@@ -4218,16 +4234,13 @@ function buildVolvelleChart(props, context) {
4218
4234
  checkSectors(CHART23, "segments", segments.length);
4219
4235
  rings.push({ label: formatCategory(datum.label, locale), segments: segments.map((s) => formatCategory(s, locale)), given: index });
4220
4236
  });
4221
- if (usesDemo && (props.indexRing !== void 0 || props.indexValue !== void 0)) {
4222
- warnValue(CHART23, props.indexValue !== void 0 ? "indexValue" : "indexRing", "Without `data` the demo keeps its own index; `indexRing` and `indexValue` apply to your rings.");
4223
- }
4224
- let ringIndex = usesDemo || props.indexRing === void 0 ? 0 : rings.findIndex((r) => r.given === props.indexRing);
4237
+ let ringIndex = props.indexRing === void 0 ? 0 : rings.findIndex((r) => r.given === props.indexRing);
4225
4238
  if (ringIndex < 0) {
4226
4239
  if (rings.length > 0) warnValue(CHART23, "indexRing", `Ring ${String(props.indexRing)} does not exist or has no segments; the index reads the first ring.`);
4227
4240
  ringIndex = 0;
4228
4241
  }
4229
4242
  const turned = rings[ringIndex];
4230
- let segment = turned && !usesDemo && props.indexValue !== void 0 ? turned.segments.indexOf(props.indexValue) : 0;
4243
+ let segment = turned && props.indexValue !== void 0 ? turned.segments.indexOf(props.indexValue) : 0;
4231
4244
  if (segment < 0) {
4232
4245
  warnValue(CHART23, "indexValue", `"${String(props.indexValue)}" is not on ring "${turned?.label}"; the index reads its first segment.`);
4233
4246
  segment = 0;
@@ -4765,6 +4778,239 @@ var bubbleChart = /* @__PURE__ */ Object.freeze({
4765
4778
  build: (props, context) => buildPoints(FAMILY6, props, context, BUBBLE_CHART_DEMO)
4766
4779
  });
4767
4780
 
4768
- export { DEFAULTS, DISPLAY_FACE_PROBE, FORCED_PRECISION_QUERY, LINE_CHART_DEMO, LINE_CHART_DEMO_KEYS, MIN_TOUCH_TARGET, NAVIGATION_KEYS, NullInker, PATH_BYTE_BUDGET, SilverpointError, __setDiagnosticSink, activityGrid, addDays, areaChart, bandScale, barChart, bubbleChart, bulletChart, candlestickChart, checkTypeface, chordRing, circlePath, composedChart, coxcombChart, createPrng, deriveSeed, diagnose, donutChart, enforceHeightening, expandDomain, extent, fnv1a32, funnelChart, gaugeArc, heatmapChart, instanceId, isoDate, kpiCard, lineChart, linearScale, max, meterChart, min, orbitChart, parseGeometry, parseIsoDate, pathBytes, polarBarChart, pyramidChart, radarChart, radialArcGroup, radialRings, rangeBandChart, readout, reduceInteraction, registerInker, resolveActive, resolveConfig, resolveInker, resolveSeed, round2, roundGeometry, roundPathData, sameActive, sankeyChart, scatterChart, serializeGeometry, sparklineRows, stackedBarChart, stepActive, stepChart, streamChart, svgString, toSvgPoint, toSvgView, treemapChart, volvelleChart, waterfallChart, weekday, windRose };
4781
+ // src/dashboard/defaults.ts
4782
+ var BREAKPOINTS = ["sm", "md", "lg"];
4783
+ var DASHBOARD = "Dashboard";
4784
+ var MAX_COLUMNS = 12;
4785
+ var MAX_ROW_SPAN = 6;
4786
+ var DASHBOARD_DEFAULTS = /* @__PURE__ */ Object.freeze({
4787
+ columns: /* @__PURE__ */ Object.freeze({ sm: 1, md: 2, lg: 4 }),
4788
+ rowHeight: 240,
4789
+ gap: 16,
4790
+ span: 1,
4791
+ headingLevel: 2,
4792
+ ssrWidth: 1200
4793
+ });
4794
+ function perBreakpoint(value, fallback) {
4795
+ const given = value !== null && typeof value === "object" ? value : void 0;
4796
+ const bare = value !== void 0 && given === void 0 ? value : void 0;
4797
+ return Object.fromEntries(BREAKPOINTS.map((bp) => [bp, bare ?? given?.[bp] ?? fallback[bp]]));
4798
+ }
4799
+ function corrected(property, given, used, rule) {
4800
+ if (process.env.NODE_ENV !== "production") {
4801
+ diagnose("SP002", DASHBOARD, { property, message: `${String(given)} is not ${rule}; ${used} is used.` });
4802
+ }
4803
+ return used;
4804
+ }
4805
+ var isInteger = (n) => typeof n === "number" && Number.isInteger(n);
4806
+ function resolveLayout(layout) {
4807
+ const asked = perBreakpoint(layout?.columns, DASHBOARD_DEFAULTS.columns);
4808
+ const columns = Object.fromEntries(
4809
+ BREAKPOINTS.map((bp) => {
4810
+ const n = asked[bp];
4811
+ const ok = isInteger(n) && n >= 1 && n <= MAX_COLUMNS;
4812
+ return [bp, ok ? n : corrected(`columns.${bp}`, n, DASHBOARD_DEFAULTS.columns[bp], `an integer from 1 to ${MAX_COLUMNS}`)];
4813
+ })
4814
+ );
4815
+ const rowHeight = layout?.rowHeight;
4816
+ const gap = layout?.gap;
4817
+ const one = { sm: 1, md: 1, lg: 1 };
4818
+ return {
4819
+ columns,
4820
+ rowHeight: rowHeight === void 0 ? DASHBOARD_DEFAULTS.rowHeight : Number.isFinite(rowHeight) && rowHeight > 0 ? rowHeight : corrected("rowHeight", rowHeight, DASHBOARD_DEFAULTS.rowHeight, "a number above 0"),
4821
+ gap: gap === void 0 ? DASHBOARD_DEFAULTS.gap : Number.isFinite(gap) && gap >= 0 ? gap : corrected("gap", gap, DASHBOARD_DEFAULTS.gap, "a number of 0 or more"),
4822
+ cells: (layout?.cells ?? []).map((cell, index) => {
4823
+ const col = perBreakpoint(cell.colSpan, one);
4824
+ const row = perBreakpoint(cell.rowSpan, one);
4825
+ const where = `cells[${index}]`;
4826
+ const span = Object.fromEntries(
4827
+ BREAKPOINTS.map((bp) => {
4828
+ let c = col[bp];
4829
+ if (!isInteger(c) || c < 1) c = corrected(`${where}.colSpan.${bp}`, c, 1, "an integer of 1 or more");
4830
+ else if (c > columns[bp]) {
4831
+ if (process.env.NODE_ENV !== "production") {
4832
+ diagnose("SP014", DASHBOARD, { property: `${where}.colSpan.${bp}`, message: `Cell "${cell.id}" spans ${c} columns at ${bp}, which has ${columns[bp]}; it spans ${columns[bp]}.` });
4833
+ }
4834
+ c = columns[bp];
4835
+ }
4836
+ let r = row[bp];
4837
+ if (!isInteger(r)) r = corrected(`${where}.rowSpan.${bp}`, r, 1, `an integer from 1 to ${MAX_ROW_SPAN}`);
4838
+ else if (r < 1 || r > MAX_ROW_SPAN) r = corrected(`${where}.rowSpan.${bp}`, r, Math.min(Math.max(r, 1), MAX_ROW_SPAN), `an integer from 1 to ${MAX_ROW_SPAN}`);
4839
+ return [bp, { col: c, row: r }];
4840
+ })
4841
+ );
4842
+ return { id: cell.id, span };
4843
+ })
4844
+ };
4845
+ }
4846
+
4847
+ // src/dashboard/resolve.ts
4848
+ var UNSAFE = /[^A-Za-z0-9_.:-]+/g;
4849
+ var MD_FROM = 640;
4850
+ var LG_FROM = 1024;
4851
+ var PROBE = 100;
4852
+ function safeIdOf(id) {
4853
+ return id.replace(UNSAFE, "-");
4854
+ }
4855
+ function warn(code, property, message) {
4856
+ if (process.env.NODE_ENV !== "production") diagnose(code, DASHBOARD, { property, message });
4857
+ }
4858
+ function breakpointOf(width) {
4859
+ return width >= LG_FROM ? "lg" : width >= MD_FROM ? "md" : "sm";
4860
+ }
4861
+ function nominalBox(layout, span, width) {
4862
+ const bp = breakpointOf(width);
4863
+ const { col, row } = span[bp];
4864
+ const cols = layout.columns[bp];
4865
+ const { gap, rowHeight } = layout;
4866
+ return {
4867
+ width: round2((width - gap * (cols - 1)) / cols * col + gap * (col - 1)),
4868
+ height: round2(rowHeight * row + gap * (row - 1))
4869
+ };
4870
+ }
4871
+ function cellStyle(span) {
4872
+ const style = {};
4873
+ for (const bp of BREAKPOINTS) style[`--sp-cell-col-${bp}`] = String(span[bp].col);
4874
+ for (const bp of BREAKPOINTS) style[`--sp-cell-row-${bp}`] = String(span[bp].row);
4875
+ return style;
4876
+ }
4877
+ function resolveDashboard(props, childCells) {
4878
+ const layout = resolveLayout(props.layout);
4879
+ let ssrWidth = DASHBOARD_DEFAULTS.ssrWidth;
4880
+ if (props.ssrWidth !== void 0) {
4881
+ if (Number.isFinite(props.ssrWidth) && props.ssrWidth > 0) ssrWidth = props.ssrWidth;
4882
+ else warn("SP002", "ssrWidth", `${String(props.ssrWidth)} is not a width above 0; ${ssrWidth} is used.`);
4883
+ }
4884
+ const declared = /* @__PURE__ */ new Map();
4885
+ const order = [];
4886
+ for (const cell of layout.cells) {
4887
+ if (declared.has(cell.id)) {
4888
+ warn("SP015", "layout.cells", `Layout cell "${cell.id}" is declared twice; the later declaration is ignored.`);
4889
+ continue;
4890
+ }
4891
+ declared.set(cell.id, cell.span);
4892
+ order.push(cell.id);
4893
+ }
4894
+ const holder = /* @__PURE__ */ new Map();
4895
+ const unplaced = [];
4896
+ childCells.forEach((cell, index) => {
4897
+ if (cell === void 0) {
4898
+ unplaced.push(index);
4899
+ } else if (!declared.has(cell)) {
4900
+ warn("SP015", "cell", `Child ${index} names cell "${cell}", which the layout does not declare; it is placed in source order, span 1.`);
4901
+ unplaced.push(index);
4902
+ } else if (holder.has(cell)) {
4903
+ warn("SP015", "cell", `Children ${holder.get(cell)} and ${index} both name cell "${cell}"; the later is placed in source order, span 1.`);
4904
+ unplaced.push(index);
4905
+ } else {
4906
+ holder.set(cell, index);
4907
+ }
4908
+ });
4909
+ for (const id of order) {
4910
+ if (!holder.has(id)) warn("SP015", "layout.cells", `Layout cell "${id}" has no child; it is dropped.`);
4911
+ }
4912
+ const single = { sm: { col: 1, row: 1 }, md: { col: 1, row: 1 }, lg: { col: 1, row: 1 } };
4913
+ const base = safeIdOf(props.id);
4914
+ const taken = /* @__PURE__ */ new Set();
4915
+ const chartIdOf = (token) => {
4916
+ const stem = `${base}--${token.replace(UNSAFE, "-")}`;
4917
+ let id = stem;
4918
+ for (let n = 2; taken.has(id); n += 1) id = `${stem}-${n}`;
4919
+ taken.add(id);
4920
+ return id;
4921
+ };
4922
+ const cellOf = (id, child, span, token) => ({
4923
+ id,
4924
+ child,
4925
+ chartId: chartIdOf(token),
4926
+ span,
4927
+ nominal: nominalBox(layout, span, ssrWidth),
4928
+ style: cellStyle(span)
4929
+ });
4930
+ const cells = [
4931
+ ...order.filter((id) => holder.has(id)).map((id) => cellOf(id, holder.get(id), declared.get(id), id)),
4932
+ ...unplaced.map((index) => cellOf(String(index), index, single, String(index)))
4933
+ ];
4934
+ const style = {};
4935
+ for (const bp of BREAKPOINTS) style[`--sp-dashboard-columns-${bp}`] = String(layout.columns[bp]);
4936
+ style["--sp-dashboard-row-height"] = `${layout.rowHeight}px`;
4937
+ style["--sp-dashboard-layout-gap"] = `${layout.gap}px`;
4938
+ return { id: props.id, cells, style };
4939
+ }
4940
+ function cellChartBox(cell, chartProps, recipe) {
4941
+ const width = chartProps.width ?? cell.width;
4942
+ if (chartProps.height !== void 0) return { width, height: chartProps.height };
4943
+ let outer;
4944
+ if (recipe && width > 0) {
4945
+ const model = recipe.build({ ...chartProps, width, height: PROBE }, { id: "sp-probe", width, locale: "en", emptyState: { text: "", rule: false }, domainPadding: 0 });
4946
+ if (model.status !== "deferred") outer = model.geometry.viewBox.height;
4947
+ }
4948
+ outer ??= cardLayout(chartProps, { width, areaHeight: PROBE, chrome: chartProps.chrome ?? "card", locale: "en" }).viewBox.height;
4949
+ return { width, height: Math.max(1, round2(cell.height - (outer - PROBE))) };
4950
+ }
4951
+
4952
+ // src/dashboard/view.ts
4953
+ function dashboardView(props, children) {
4954
+ const model = resolveDashboard(props, children.map((c) => c.cell));
4955
+ if (props.title === void 0 && props.label === void 0 && process.env.NODE_ENV !== "production") {
4956
+ diagnose("SP002", "Dashboard", { property: "title", message: "Name the region with `title` or `label`." });
4957
+ }
4958
+ const base = safeIdOf(props.id);
4959
+ const ground = props.ground ?? DEFAULTS.ground;
4960
+ const groundName = typeof ground === "string" ? ground : ground.name;
4961
+ const titled = props.title !== void 0;
4962
+ const heading = titled ? { level: props.headingLevel ?? DASHBOARD_DEFAULTS.headingLevel, id: `${base}-title`, text: props.title } : void 0;
4963
+ const description = props.description !== void 0 ? { id: `${base}-desc`, text: props.description } : void 0;
4964
+ const config = {};
4965
+ for (const key of ["ground", "substrate", "mode", "locale"]) {
4966
+ if (props[key] !== void 0) config[key] = props[key];
4967
+ }
4968
+ return {
4969
+ model,
4970
+ section: {
4971
+ className: ["sp-dashboard", `sp-ground-${groundName}`, props.className].filter(Boolean).join(" "),
4972
+ substrate: props.substrate ?? DEFAULTS.substrate,
4973
+ ...heading ? { labelledby: heading.id } : { label: props.label },
4974
+ ...description ? { describedby: description.id } : {},
4975
+ style: model.style
4976
+ },
4977
+ ...heading ? { heading } : {},
4978
+ ...description ? { description } : {},
4979
+ cells: model.cells.map((cell) => ({
4980
+ child: cell.child,
4981
+ labelledby: `${children[cell.child]?.id ?? cell.chartId}-title`,
4982
+ style: cell.style,
4983
+ context: { chartId: cell.chartId, box: cell.nominal, config }
4984
+ }))
4985
+ };
4986
+ }
4987
+ function inCell(props, cell, recipe) {
4988
+ if (!cell) return { props, width: props.width };
4989
+ const box = cellChartBox(cell.box, props, recipe);
4990
+ const merged = { ...props };
4991
+ for (const [key, value] of Object.entries(cell.config)) merged[key] ??= value;
4992
+ return {
4993
+ props: { ...merged, id: props.id ?? cell.chartId, height: props.height ?? box.height },
4994
+ width: props.width ?? box.width
4995
+ };
4996
+ }
4997
+
4998
+ // src/dashboard/link.ts
4999
+ function linkedItems(model, key, value) {
5000
+ const { hitAreas } = model.geometry;
5001
+ if (hitAreas.length > 0 && !hitAreas.some((hit) => key in hit.datum) && process.env.NODE_ENV !== "production") {
5002
+ diagnose("SP016", model.chart, { property: "link.key", message: `No datum has a "${key}" field.` });
5003
+ }
5004
+ return hitAreas.flatMap((hit, index) => hit.datum[key] === value ? [index] : []);
5005
+ }
5006
+ function linkFrom(key, source, active) {
5007
+ return active ? { key, value: active.datum[key], source } : null;
5008
+ }
5009
+ function linkedMarks(model, state) {
5010
+ if (!state || state.source === model.id) return [];
5011
+ return linkedItems(model, state.key, state.value).map((index) => readout(model, toActiveItem(model.geometry.hitAreas[index])).marker.d);
5012
+ }
5013
+
5014
+ export { DASHBOARD_DEFAULTS, DEFAULTS, DISPLAY_FACE_PROBE, FORCED_PRECISION_QUERY, LINE_CHART_DEMO, LINE_CHART_DEMO_KEYS, MIN_TOUCH_TARGET, NAVIGATION_KEYS, NullInker, PATH_BYTE_BUDGET, SilverpointError, __setDiagnosticSink, activityGrid, addDays, areaChart, bandScale, barChart, breakpointOf, bubbleChart, bulletChart, candlestickChart, cellChartBox, checkTypeface, chordRing, circlePath, composedChart, coxcombChart, createPrng, dashboardView, deriveSeed, diagnose, donutChart, enforceHeightening, expandDomain, extent, fnv1a32, funnelChart, gaugeArc, heatmapChart, inCell, instanceId, isoDate, kpiCard, lineChart, linearScale, linkFrom, linkedItems, linkedMarks, max, meterChart, min, orbitChart, parseGeometry, parseIsoDate, pathBytes, perBreakpoint, polarBarChart, pyramidChart, radarChart, radialArcGroup, radialRings, rangeBandChart, readout, reduceInteraction, registerInker, resolveActive, resolveConfig, resolveDashboard, resolveInker, resolveLayout, resolveSeed, round2, roundGeometry, roundPathData, sameActive, sankeyChart, scatterChart, serializeGeometry, sparklineRows, stackedBarChart, stepActive, stepChart, streamChart, svgString, toSvgPoint, toSvgView, treemapChart, volvelleChart, waterfallChart, weekday, windRose };
4769
5015
  //# sourceMappingURL=index.js.map
4770
5016
  //# sourceMappingURL=index.js.map