@geomak/ui 7.16.0 → 7.17.1

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.cjs CHANGED
@@ -5092,12 +5092,52 @@ function useTargetBbox(ref) {
5092
5092
  }
5093
5093
  var TOOLTIP_WIDTH = 280;
5094
5094
  var TOOLTIP_GAP = 12;
5095
- function tooltipStyleFor(bbox, placement) {
5096
- const pl = placement ?? "right";
5097
- if (pl === "right") return { left: bbox.right + TOOLTIP_GAP, top: bbox.top + bbox.height / 2, transform: "translateY(-50%)", width: TOOLTIP_WIDTH };
5098
- if (pl === "left") return { left: bbox.left - TOOLTIP_WIDTH - TOOLTIP_GAP, top: bbox.top + bbox.height / 2, transform: "translateY(-50%)", width: TOOLTIP_WIDTH };
5099
- if (pl === "bottom") return { left: bbox.left + bbox.width / 2, top: bbox.bottom + TOOLTIP_GAP, transform: "translateX(-50%)", width: TOOLTIP_WIDTH };
5100
- return { left: bbox.left + bbox.width / 2, top: bbox.top - TOOLTIP_GAP, transform: "translate(-50%, -100%)", width: TOOLTIP_WIDTH };
5095
+ function tooltipStyleFor(bbox, placement, tooltipHeight) {
5096
+ const GAP2 = TOOLTIP_GAP;
5097
+ const W = TOOLTIP_WIDTH;
5098
+ const H = tooltipHeight;
5099
+ const vw = window.innerWidth;
5100
+ const vh = window.innerHeight;
5101
+ let side = placement ?? "right";
5102
+ if (side === "right" && bbox.right + GAP2 + W > vw) side = "left";
5103
+ if (side === "left" && bbox.left - W - GAP2 < 0) side = "right";
5104
+ if (side === "bottom" && bbox.bottom + GAP2 + H > vh) side = "top";
5105
+ if (side === "top" && bbox.top - GAP2 - H < 0) side = "bottom";
5106
+ let left;
5107
+ let top;
5108
+ let transform;
5109
+ if (side === "right") {
5110
+ left = bbox.right + GAP2;
5111
+ top = bbox.top + bbox.height / 2;
5112
+ transform = "translateY(-50%)";
5113
+ } else if (side === "left") {
5114
+ left = bbox.left - W - GAP2;
5115
+ top = bbox.top + bbox.height / 2;
5116
+ transform = "translateY(-50%)";
5117
+ } else if (side === "bottom") {
5118
+ left = bbox.left + bbox.width / 2;
5119
+ top = bbox.bottom + GAP2;
5120
+ transform = "translateX(-50%)";
5121
+ } else {
5122
+ left = bbox.left + bbox.width / 2;
5123
+ top = bbox.top - GAP2;
5124
+ transform = "translate(-50%, -100%)";
5125
+ }
5126
+ if (side === "top" || side === "bottom") {
5127
+ left = Math.max(GAP2 + W / 2, Math.min(left, vw - W / 2 - GAP2));
5128
+ } else {
5129
+ left = Math.max(GAP2, Math.min(left, vw - W - GAP2));
5130
+ }
5131
+ if (H > 0) {
5132
+ if (side === "right" || side === "left") {
5133
+ top = Math.max(GAP2 + H / 2, Math.min(top, vh - GAP2 - H / 2));
5134
+ } else if (side === "bottom") {
5135
+ top = Math.max(GAP2, Math.min(top, vh - GAP2 - H));
5136
+ } else {
5137
+ top = Math.max(GAP2 + H, Math.min(top, vh - GAP2));
5138
+ }
5139
+ }
5140
+ return { left, top, transform, width: W };
5101
5141
  }
5102
5142
  function useFocusTrap(containerRef, active) {
5103
5143
  React36.useEffect(() => {
@@ -5145,6 +5185,11 @@ function Wizard({
5145
5185
  const reduced = framerMotion.useReducedMotion();
5146
5186
  const [open, setOpen] = React36.useState(() => steps.length > 0 && !readDismissed(storageKey));
5147
5187
  const [activeIndex, setActiveIndex] = React36.useState(0);
5188
+ const [tooltipHeight, setTooltipHeight] = React36.useState(0);
5189
+ React36.useLayoutEffect(() => {
5190
+ const h = tooltipRef.current?.offsetHeight ?? 0;
5191
+ if (h > 0) setTooltipHeight((prev) => prev === h ? prev : h);
5192
+ }, [activeIndex, open]);
5148
5193
  const step = steps[activeIndex];
5149
5194
  const bbox = useTargetBbox(step?.stepRef);
5150
5195
  useFocusTrap(tooltipRef, open);
@@ -5197,7 +5242,7 @@ function Wizard({
5197
5242
  right: 0,
5198
5243
  height: bbox.height + SPOT_PAD * 2
5199
5244
  } : { display: "none" };
5200
- const tooltipStyle = bbox ? tooltipStyleFor(bbox, step?.placement) : { display: "none" };
5245
+ const tooltipStyle = bbox ? tooltipStyleFor(bbox, step?.placement, tooltipHeight) : { display: "none" };
5201
5246
  const isLast = activeIndex === steps.length - 1;
5202
5247
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5203
5248
  children,
@@ -11181,6 +11226,36 @@ function useJwt(token) {
11181
11226
  const isValid = decoded.payload != null && !isExpired;
11182
11227
  return { payload: decoded.payload, header: decoded.header, expiresAt, isExpired, isValid, raw: token ?? null };
11183
11228
  }
11229
+ function useDownload() {
11230
+ const saveBlob = React36.useCallback((blob, fileName) => {
11231
+ if (typeof window === "undefined") return;
11232
+ const url = URL.createObjectURL(blob);
11233
+ const a = document.createElement("a");
11234
+ a.href = url;
11235
+ a.download = fileName;
11236
+ a.click();
11237
+ setTimeout(() => URL.revokeObjectURL(url), 0);
11238
+ }, []);
11239
+ const saveFromBase64 = React36.useCallback((base64, fileName, mimeType) => {
11240
+ let payload = base64;
11241
+ let inferredMime;
11242
+ if (base64.startsWith("data:")) {
11243
+ const commaIdx = base64.indexOf(",");
11244
+ if (commaIdx !== -1) {
11245
+ const header = base64.slice(5, commaIdx);
11246
+ const semiIdx = header.indexOf(";");
11247
+ if (semiIdx !== -1) inferredMime = header.slice(0, semiIdx);
11248
+ payload = base64.slice(commaIdx + 1);
11249
+ }
11250
+ }
11251
+ const binary = atob(payload);
11252
+ const bytes = new Uint8Array(binary.length);
11253
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
11254
+ const blob = new Blob([bytes], { type: mimeType ?? inferredMime ?? "application/octet-stream" });
11255
+ saveBlob(blob, fileName);
11256
+ }, [saveBlob]);
11257
+ return { saveBlob, saveFromBase64 };
11258
+ }
11184
11259
  var INVALID_WS_CHARS = /[:\\/?\*[\]]/g;
11185
11260
  function sanitizeName(name) {
11186
11261
  return name.replace(INVALID_WS_CHARS, "_").slice(0, 31) || "Sheet";
@@ -11189,6 +11264,7 @@ var xlsxPromise2 = null;
11189
11264
  var loadXlsx2 = () => xlsxPromise2 ??= import('xlsx');
11190
11265
  function useExcel() {
11191
11266
  const [isExporting, setIsExporting] = React36.useState(false);
11267
+ const { saveBlob } = useDownload();
11192
11268
  const exportSheets = React36.useCallback(async (sheets, options = {}) => {
11193
11269
  const { fileName = "export", onSave } = options;
11194
11270
  setIsExporting(true);
@@ -11240,12 +11316,12 @@ function useExcel() {
11240
11316
  if (onSave) {
11241
11317
  await onSave(blob, name);
11242
11318
  } else {
11243
- downloadBlob(blob, name);
11319
+ saveBlob(blob, name);
11244
11320
  }
11245
11321
  } finally {
11246
11322
  setIsExporting(false);
11247
11323
  }
11248
- }, []);
11324
+ }, [saveBlob]);
11249
11325
  const readWorkbook = React36.useCallback(async (source) => {
11250
11326
  const XLSX = await loadXlsx2();
11251
11327
  let bytes;
@@ -11281,6 +11357,7 @@ var jspdfPromise2 = null;
11281
11357
  var loadJspdf2 = () => jspdfPromise2 ??= import('jspdf');
11282
11358
  function usePdf() {
11283
11359
  const [isExporting, setIsExporting] = React36.useState(false);
11360
+ const { saveBlob } = useDownload();
11284
11361
  const exportCanvases = React36.useCallback(async (pages, options = {}) => {
11285
11362
  const { fileName = "report", onSave, orientation = "landscape" } = options;
11286
11363
  const validPages = pages.filter((p) => p.canvas.width !== 0 && p.canvas.height !== 0);
@@ -11315,17 +11392,12 @@ function usePdf() {
11315
11392
  if (onSave) {
11316
11393
  await onSave(blob, name);
11317
11394
  } else {
11318
- const url = URL.createObjectURL(blob);
11319
- const a = document.createElement("a");
11320
- a.href = url;
11321
- a.download = name;
11322
- a.click();
11323
- URL.revokeObjectURL(url);
11395
+ saveBlob(blob, name);
11324
11396
  }
11325
11397
  } finally {
11326
11398
  setIsExporting(false);
11327
11399
  }
11328
- }, []);
11400
+ }, [saveBlob]);
11329
11401
  return { exportCanvases, isExporting };
11330
11402
  }
11331
11403
  var GRADIENT = "radial-gradient(ellipse 80% 60% at 50% 0%, color-mix(in oklab, var(--color-accent) 12%, transparent), transparent 70%)";
@@ -12015,6 +12087,7 @@ exports.runFieldRules = runFieldRules;
12015
12087
  exports.scorePassword = scorePassword;
12016
12088
  exports.useBreakpoint = useBreakpoint;
12017
12089
  exports.useCart = useCart;
12090
+ exports.useDownload = useDownload;
12018
12091
  exports.useExcel = useExcel;
12019
12092
  exports.useFieldArray = useFieldArray;
12020
12093
  exports.useForm = useForm;