@bicharts/chart-host 0.5.28 → 0.5.30
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.mjs +126 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/qualifyGroups.d.ts +31 -0
- package/dist/types/reviewDialog.d.ts +23 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -446,6 +446,129 @@ function actionFor(v) {
|
|
|
446
446
|
return { kind: "keep", reason: v.errorMessage ?? v.verdictReason ?? "review failed", userVisible: false };
|
|
447
447
|
}
|
|
448
448
|
|
|
449
|
+
// src/reviewDialog.ts
|
|
450
|
+
var DEFAULT_TEXT = {
|
|
451
|
+
title: "Apply improvements?",
|
|
452
|
+
fallbackReason: "The AI review suggests a change to this chart.",
|
|
453
|
+
cost: "Applying creates a new chart version and uses a generation. Declining costs nothing.",
|
|
454
|
+
decline: "No thanks",
|
|
455
|
+
accept: "Apply"
|
|
456
|
+
};
|
|
457
|
+
function askApplyImprovements(container, reason, opts = {}) {
|
|
458
|
+
return new Promise((resolve) => {
|
|
459
|
+
try {
|
|
460
|
+
const minW = opts.minWidth ?? 260;
|
|
461
|
+
const minH = opts.minHeight ?? 170;
|
|
462
|
+
const w = container.clientWidth || container.offsetWidth || 0;
|
|
463
|
+
const h = container.clientHeight || container.offsetHeight || 0;
|
|
464
|
+
if (w < minW || h < minH) {
|
|
465
|
+
resolve(false);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const text = { ...DEFAULT_TEXT, ...opts.text ?? {} };
|
|
469
|
+
const doc = container.ownerDocument;
|
|
470
|
+
const ov = doc.createElement("div");
|
|
471
|
+
ov.id = opts.id ?? "bic-review-ask-overlay";
|
|
472
|
+
if (opts.overlayClass) ov.className = opts.overlayClass;
|
|
473
|
+
else ov.style.cssText = "position:absolute;inset:0;background:rgba(0,0,0,0.55);z-index:110;";
|
|
474
|
+
ov.style.display = "flex";
|
|
475
|
+
ov.style.alignItems = "center";
|
|
476
|
+
ov.style.justifyContent = "center";
|
|
477
|
+
const card = doc.createElement("div");
|
|
478
|
+
if (opts.cardClass) card.className = opts.cardClass;
|
|
479
|
+
else card.style.cssText = "background:#ffffff;color:#1f2937;border-radius:10px;box-shadow:0 6px 24px rgba(0,0,0,0.22);";
|
|
480
|
+
card.style.width = "auto";
|
|
481
|
+
card.style.height = "auto";
|
|
482
|
+
card.style.maxWidth = "min(420px, 90%)";
|
|
483
|
+
card.style.maxHeight = "90%";
|
|
484
|
+
card.style.overflow = "auto";
|
|
485
|
+
card.style.padding = "16px 18px";
|
|
486
|
+
card.style.display = "flex";
|
|
487
|
+
card.style.flexDirection = "column";
|
|
488
|
+
card.style.gap = "10px";
|
|
489
|
+
card.style.boxSizing = "border-box";
|
|
490
|
+
const head = doc.createElement("div");
|
|
491
|
+
head.style.cssText = "font:600 13px 'Segoe UI',system-ui,sans-serif;";
|
|
492
|
+
head.textContent = text.title;
|
|
493
|
+
const why = doc.createElement("div");
|
|
494
|
+
why.style.cssText = "font:400 12px/1.45 'Segoe UI',system-ui,sans-serif;white-space:normal;";
|
|
495
|
+
why.textContent = reason || text.fallbackReason;
|
|
496
|
+
const cost = doc.createElement("div");
|
|
497
|
+
cost.style.cssText = "font:400 11px/1.4 'Segoe UI',system-ui,sans-serif;opacity:0.75;white-space:normal;";
|
|
498
|
+
cost.textContent = text.cost;
|
|
499
|
+
const row = doc.createElement("div");
|
|
500
|
+
row.style.cssText = "display:flex;gap:8px;justify-content:flex-end;margin-top:4px;";
|
|
501
|
+
const no = doc.createElement("button");
|
|
502
|
+
no.type = "button";
|
|
503
|
+
no.textContent = text.decline;
|
|
504
|
+
const yes = doc.createElement("button");
|
|
505
|
+
yes.type = "button";
|
|
506
|
+
yes.textContent = text.accept;
|
|
507
|
+
yes.style.cssText = "font-weight:600;";
|
|
508
|
+
let done = false;
|
|
509
|
+
const finish = (v) => {
|
|
510
|
+
if (done) return;
|
|
511
|
+
done = true;
|
|
512
|
+
try {
|
|
513
|
+
if (ov.parentElement) ov.parentElement.removeChild(ov);
|
|
514
|
+
} catch {
|
|
515
|
+
}
|
|
516
|
+
doc.removeEventListener("keydown", onKey, true);
|
|
517
|
+
resolve(v);
|
|
518
|
+
};
|
|
519
|
+
const onKey = (e) => {
|
|
520
|
+
if (e.key === "Escape") {
|
|
521
|
+
e.stopPropagation();
|
|
522
|
+
finish(false);
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
no.addEventListener("click", () => finish(false));
|
|
526
|
+
yes.addEventListener("click", () => finish(true));
|
|
527
|
+
ov.addEventListener("click", (e) => {
|
|
528
|
+
if (e.target === ov) finish(false);
|
|
529
|
+
});
|
|
530
|
+
card.addEventListener("click", (e) => e.stopPropagation());
|
|
531
|
+
doc.addEventListener("keydown", onKey, true);
|
|
532
|
+
row.append(no, yes);
|
|
533
|
+
card.append(head, why, cost, row);
|
|
534
|
+
ov.appendChild(card);
|
|
535
|
+
container.appendChild(ov);
|
|
536
|
+
try {
|
|
537
|
+
yes.focus();
|
|
538
|
+
} catch {
|
|
539
|
+
}
|
|
540
|
+
} catch {
|
|
541
|
+
resolve(false);
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// src/qualifyGroups.ts
|
|
547
|
+
function newQualifyGroupState() {
|
|
548
|
+
return { preview: false, previewClosed: false, projected: false, notRecommended: false };
|
|
549
|
+
}
|
|
550
|
+
function qualifyGroupHeadingFor(row, state) {
|
|
551
|
+
const preview = row.isPreview === true;
|
|
552
|
+
if (preview && !state.preview && !state.notRecommended) {
|
|
553
|
+
state.preview = true;
|
|
554
|
+
return "preview";
|
|
555
|
+
}
|
|
556
|
+
if (state.preview && !state.previewClosed && !preview) {
|
|
557
|
+
state.previewClosed = true;
|
|
558
|
+
return "main";
|
|
559
|
+
}
|
|
560
|
+
const projected = typeof row.viaProjection === "string" && row.viaProjection !== "";
|
|
561
|
+
if (projected && !state.projected && !state.notRecommended) {
|
|
562
|
+
state.projected = true;
|
|
563
|
+
return "projected";
|
|
564
|
+
}
|
|
565
|
+
if (row.recommended === false && !state.notRecommended) {
|
|
566
|
+
state.notRecommended = true;
|
|
567
|
+
return "notRecommended";
|
|
568
|
+
}
|
|
569
|
+
return null;
|
|
570
|
+
}
|
|
571
|
+
|
|
449
572
|
// src/index.ts
|
|
450
573
|
function registerCityTable(packed) {
|
|
451
574
|
L3(packed);
|
|
@@ -479,6 +602,7 @@ export {
|
|
|
479
602
|
SELECTION_ACTIVE_CLASS,
|
|
480
603
|
XFILTER_REFRESH_EVENT,
|
|
481
604
|
actionFor,
|
|
605
|
+
askApplyImprovements,
|
|
482
606
|
bareBase64,
|
|
483
607
|
buildRenderPayload,
|
|
484
608
|
buildReviewWire,
|
|
@@ -492,7 +616,9 @@ export {
|
|
|
492
616
|
geoAssetFor,
|
|
493
617
|
geoFromCache,
|
|
494
618
|
loadGeo,
|
|
619
|
+
newQualifyGroupState,
|
|
495
620
|
planTrivialChart,
|
|
621
|
+
qualifyGroupHeadingFor,
|
|
496
622
|
rasterizeSvgToPngDataUrl,
|
|
497
623
|
registerCityTable,
|
|
498
624
|
registerGeo,
|
package/dist/types/index.d.ts
CHANGED
|
@@ -10,3 +10,5 @@ export { createMarkResolver, type MarkResolver, type MarkResolverEnv } from "./s
|
|
|
10
10
|
export { planTrivialChart, compileTrivialSource, type TrivialPlan, type TrivialShapeKind } from "./trivial";
|
|
11
11
|
export { captureSvgSnapshot, svgToDataUrl, svgNaturalSize, rasterizeSvgToPngDataUrl, type SnapshotOptions } from "./snapshot";
|
|
12
12
|
export { shouldReview, buildReviewWire, bareBase64, actionFor, type ReviewGate, type ReviewWire, type ReviewVerdict, type ReviewAction } from "./review";
|
|
13
|
+
export { askApplyImprovements, type ReviewDialogOptions, type ReviewDialogText } from "./reviewDialog";
|
|
14
|
+
export { qualifyGroupHeadingFor, newQualifyGroupState, type QualifyGroupRow, type QualifyGroupState, type QualifyGroupHeading, } from "./qualifyGroups";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** One row of a qualify result, in the only shape this logic cares about. */
|
|
2
|
+
export interface QualifyGroupRow {
|
|
3
|
+
/** Newer type, deliberately unscorable. Server-stamped from the chart catalogue. */
|
|
4
|
+
isPreview?: boolean | null;
|
|
5
|
+
/** false = the ontology says a required channel cannot be satisfied by these fields. */
|
|
6
|
+
recommended?: boolean | null;
|
|
7
|
+
/** Non-empty when the type qualifies only against an AGGREGATED projection of the shape. */
|
|
8
|
+
viaProjection?: string | null;
|
|
9
|
+
}
|
|
10
|
+
/** Which heading to write BEFORE a row, if any. */
|
|
11
|
+
export type QualifyGroupHeading = "preview" | "main" | "projected" | "notRecommended";
|
|
12
|
+
/** Carried across the loop. Create one per render with `newQualifyGroupState()`. */
|
|
13
|
+
export interface QualifyGroupState {
|
|
14
|
+
preview: boolean;
|
|
15
|
+
previewClosed: boolean;
|
|
16
|
+
projected: boolean;
|
|
17
|
+
notRecommended: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function newQualifyGroupState(): QualifyGroupState;
|
|
20
|
+
/**
|
|
21
|
+
* The heading (if any) that belongs immediately before `row`, MUTATING `state` so the caller's
|
|
22
|
+
* loop stays a plain for-of. At most one heading per row: the boundaries cannot coincide, since
|
|
23
|
+
* a row leaving the preview block is by definition not the row that opened it.
|
|
24
|
+
*
|
|
25
|
+
* ORDER OF THE CHECKS IS THE CONTRACT:
|
|
26
|
+
* 1. "preview" opens the block - but never once the not-recommended block has opened, because a
|
|
27
|
+
* preview type the ontology ruled out is NOT floated and belongs where it landed.
|
|
28
|
+
* 2. "main" closes it, at the first row that is not preview. Emitted only if a block opened.
|
|
29
|
+
* 3. "projected" and 4. "notRecommended" are unchanged from the single-host original.
|
|
30
|
+
*/
|
|
31
|
+
export declare function qualifyGroupHeadingFor(row: QualifyGroupRow, state: QualifyGroupState): QualifyGroupHeading | null;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface ReviewDialogText {
|
|
2
|
+
title?: string;
|
|
3
|
+
/** Shown when the judge supplied no reason. The judge's reason always wins when present. */
|
|
4
|
+
fallbackReason?: string;
|
|
5
|
+
cost?: string;
|
|
6
|
+
decline?: string;
|
|
7
|
+
accept?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ReviewDialogOptions {
|
|
10
|
+
/** Overlay element id. Stable so tests and log-readers can find the same dialog anywhere. */
|
|
11
|
+
id?: string;
|
|
12
|
+
/** Host chrome. When given they are ADDED to the inline baseline, so a host theme (e.g. a
|
|
13
|
+
* dark-mode card background) can win where it should while the layout stays put. */
|
|
14
|
+
overlayClass?: string;
|
|
15
|
+
cardClass?: string;
|
|
16
|
+
/** The too-small-to-ask floor. Below it the dialog is a clipped smear over the chart, and
|
|
17
|
+
* asking illegibly for permission to spend money is worse than not asking. */
|
|
18
|
+
minWidth?: number;
|
|
19
|
+
minHeight?: number;
|
|
20
|
+
text?: ReviewDialogText;
|
|
21
|
+
}
|
|
22
|
+
/** Resolves true ONLY on a deliberate click of Apply. Never rejects. */
|
|
23
|
+
export declare function askApplyImprovements(container: HTMLElement, reason: string, opts?: ReviewDialogOptions): Promise<boolean>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bicharts/chart-host",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.30",
|
|
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",
|