hazo_ui 6.16.0 → 6.19.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
@@ -2,14 +2,14 @@
2
2
  import { clsx } from 'clsx';
3
3
  import { twMerge } from 'tailwind-merge';
4
4
  import * as React27 from 'react';
5
- import React27__default, { createContext, lazy, useContext, useRef, useState, useCallback, useEffect, useMemo, Fragment, Suspense, useSyncExternalStore } from 'react';
5
+ import React27__default, { createContext, lazy, useRef, useContext, useState, useCallback, useEffect, useMemo, Fragment, Suspense, useSyncExternalStore } from 'react';
6
6
  import { generateRequestId } from 'hazo_core';
7
7
  import { withContext, setBrowserCorrelationId } from 'hazo_core/client';
8
8
  import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
9
9
  import { Slot } from '@radix-ui/react-slot';
10
10
  import { cva } from 'class-variance-authority';
11
11
  import * as DialogPrimitive from '@radix-ui/react-dialog';
12
- import { X, ChevronDown, ChevronUp, Check, Circle, ChevronRight, ChevronLeft, Filter, Plus, ArrowUpDown, icons, Loader2, AlertTriangle, OctagonAlert, CheckCircle2, ChevronsUpDown, Download, Share2, Copy, Menu, GripVertical, ArrowUp, ArrowDown, Pencil, Calendar as Calendar$1, Globe } from 'lucide-react';
12
+ import { X, ChevronDown, ChevronUp, Check, Circle, ChevronRight, GripVertical, Trash2, ChevronLeft, Filter, Plus, ArrowUpDown, icons, Loader2, AlertTriangle, OctagonAlert, CheckCircle2, ChevronsUpDown, Download, Share2, Copy, Menu, ArrowUp, ArrowDown, Pencil, Calendar as Calendar$1, Globe } from 'lucide-react';
13
13
  import * as PopoverPrimitive from '@radix-ui/react-popover';
14
14
  import * as SelectPrimitive from '@radix-ui/react-select';
15
15
  import * as TooltipPrimitive from '@radix-ui/react-tooltip';
@@ -17,7 +17,7 @@ import { DayPicker } from 'react-day-picker';
17
17
  import { format } from 'date-fns';
18
18
  import { createPackageT, resolvePackageLocale } from 'hazo_core/i18n';
19
19
  import { useSensors, useSensor, PointerSensor, KeyboardSensor, DndContext, closestCenter, pointerWithin, rectIntersection, closestCorners, DragOverlay, useDroppable } from '@dnd-kit/core';
20
- import { sortableKeyboardCoordinates, SortableContext, verticalListSortingStrategy, arrayMove, useSortable } from '@dnd-kit/sortable';
20
+ import { useSortable, sortableKeyboardCoordinates, SortableContext, verticalListSortingStrategy, arrayMove } from '@dnd-kit/sortable';
21
21
  import { CSS } from '@dnd-kit/utilities';
22
22
  import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
23
23
  import * as FaIcons from 'react-icons/fa';
@@ -866,7 +866,11 @@ var en_default = {
866
866
  "charts.pct_of_first": "% of first: {{pct}}%",
867
867
  "charts.pct_of_prev": "% of prev: {{pct}}%",
868
868
  "charts.dropoff": "Drop-off: \u2212{{value}}",
869
- "toast.missing_prefix": "Missing: {{items}}"
869
+ "toast.missing_prefix": "Missing: {{items}}",
870
+ "editable_data_table.add": "Add",
871
+ "editable_data_table.delete": "Delete row",
872
+ "editable_data_table.empty": "No rows yet. Click Add to start.",
873
+ "editable_data_table.drag_to_reorder": "Drag to reorder"
870
874
  };
871
875
  var default_t = createPackageT("hazo_ui", en_default);
872
876
  var defaultHazoUiT = default_t;
@@ -13154,7 +13158,847 @@ function useHazoEvent(pkg, event, handler) {
13154
13158
  return () => window.removeEventListener(event_name, listener);
13155
13159
  }, [pkg, event]);
13156
13160
  }
13161
+ function EditableCell({
13162
+ value,
13163
+ type,
13164
+ options,
13165
+ defaultColorValue,
13166
+ clearable,
13167
+ onCommit,
13168
+ editable,
13169
+ forceEdit,
13170
+ onTabOut,
13171
+ onEnterDown,
13172
+ onPaste,
13173
+ onActivated
13174
+ }) {
13175
+ const [editing, setEditing] = useState(false);
13176
+ const [draft, setDraft] = useState(String(value ?? ""));
13177
+ const inputRef = useRef(null);
13178
+ useEffect(() => {
13179
+ if (forceEdit) {
13180
+ setDraft(String(value ?? ""));
13181
+ setEditing(true);
13182
+ }
13183
+ }, [forceEdit]);
13184
+ useEffect(() => {
13185
+ if (editing && inputRef.current) {
13186
+ inputRef.current.focus();
13187
+ inputRef.current.select();
13188
+ if (forceEdit) {
13189
+ onActivated?.();
13190
+ }
13191
+ }
13192
+ }, [editing]);
13193
+ const commit = useCallback(() => {
13194
+ setEditing(false);
13195
+ if (type === "number") {
13196
+ const n = Number(draft);
13197
+ onCommit(Number.isNaN(n) || draft.trim() === "" ? 0 : n);
13198
+ } else {
13199
+ onCommit(draft);
13200
+ }
13201
+ }, [draft, onCommit, type]);
13202
+ const cancel = useCallback(() => {
13203
+ setEditing(false);
13204
+ setDraft(String(value ?? ""));
13205
+ }, [value]);
13206
+ const handleKeyDown = useCallback(
13207
+ (e) => {
13208
+ if (e.key === "Enter") {
13209
+ e.preventDefault();
13210
+ onEnterDown?.();
13211
+ commit();
13212
+ } else if (e.key === "Escape") {
13213
+ e.preventDefault();
13214
+ cancel();
13215
+ } else if (e.key === "Tab") {
13216
+ const direction = e.shiftKey ? -1 : 1;
13217
+ const handled = onTabOut?.(direction) ?? false;
13218
+ if (handled) {
13219
+ e.preventDefault();
13220
+ commit();
13221
+ }
13222
+ }
13223
+ },
13224
+ [commit, cancel, onTabOut, onEnterDown]
13225
+ );
13226
+ if (!editable) {
13227
+ return /* @__PURE__ */ jsx("span", { className: "text-xs truncate block px-1 py-1.5", children: String(value ?? "") });
13228
+ }
13229
+ if (type === "color") {
13230
+ const colorValue = String(
13231
+ value ?? defaultColorValue ?? "#94a3b8"
13232
+ );
13233
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5", children: [
13234
+ /* @__PURE__ */ jsx(
13235
+ "input",
13236
+ {
13237
+ type: "color",
13238
+ className: "h-7 w-8 cursor-pointer border border-border rounded p-0.5",
13239
+ value: colorValue,
13240
+ onChange: (e) => onCommit(e.target.value)
13241
+ }
13242
+ ),
13243
+ clearable && value != null && /* @__PURE__ */ jsx(
13244
+ "button",
13245
+ {
13246
+ type: "button",
13247
+ className: "text-xs leading-none text-muted-foreground hover:text-foreground px-0.5",
13248
+ onClick: () => onCommit(void 0),
13249
+ children: "\xD7"
13250
+ }
13251
+ )
13252
+ ] });
13253
+ }
13254
+ if (type === "select") {
13255
+ return /* @__PURE__ */ jsxs(
13256
+ Select,
13257
+ {
13258
+ value: String(value ?? ""),
13259
+ onValueChange: (v) => onCommit(v),
13260
+ children: [
13261
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "h-7 text-xs", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
13262
+ /* @__PURE__ */ jsx(SelectContent, { children: (options ?? []).map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt, children: opt }, opt)) })
13263
+ ]
13264
+ }
13265
+ );
13266
+ }
13267
+ if (!editing) {
13268
+ return /* @__PURE__ */ jsx(
13269
+ "span",
13270
+ {
13271
+ className: "text-xs truncate block px-1 py-1.5 cursor-text hover:bg-muted/50 rounded",
13272
+ onClick: () => {
13273
+ setDraft(String(value ?? ""));
13274
+ setEditing(true);
13275
+ },
13276
+ role: "button",
13277
+ tabIndex: 0,
13278
+ onKeyDown: (e) => {
13279
+ if (e.key === "Enter" || e.key === " ") {
13280
+ setDraft(String(value ?? ""));
13281
+ setEditing(true);
13282
+ }
13283
+ },
13284
+ children: String(value ?? "")
13285
+ }
13286
+ );
13287
+ }
13288
+ return /* @__PURE__ */ jsx(
13289
+ "input",
13290
+ {
13291
+ ref: inputRef,
13292
+ type: type === "number" ? "number" : "text",
13293
+ className: "h-7 text-xs border border-input rounded-md px-2 bg-background focus:outline-none focus:ring-1 focus:ring-ring w-full",
13294
+ value: draft,
13295
+ onChange: (e) => setDraft(e.target.value),
13296
+ onKeyDown: handleKeyDown,
13297
+ onPaste: (e) => {
13298
+ const text = e.clipboardData.getData("text/plain") || e.clipboardData.getData("text");
13299
+ if (!text) return;
13300
+ const handled = onPaste?.(text) ?? false;
13301
+ if (handled) {
13302
+ e.preventDefault();
13303
+ setEditing(false);
13304
+ }
13305
+ },
13306
+ onBlur: commit
13307
+ }
13308
+ );
13309
+ }
13310
+ var SortableRow = React27__default.memo(function SortableRow2({
13311
+ id,
13312
+ highlighted,
13313
+ children
13314
+ }) {
13315
+ const t = useHazoUiT();
13316
+ const {
13317
+ attributes,
13318
+ listeners,
13319
+ setNodeRef,
13320
+ setActivatorNodeRef,
13321
+ transform,
13322
+ transition,
13323
+ isDragging
13324
+ } = useSortable({ id });
13325
+ const style = {
13326
+ transform: CSS.Transform.toString(transform),
13327
+ transition,
13328
+ opacity: isDragging ? 0.5 : 1
13329
+ };
13330
+ return /* @__PURE__ */ jsxs(
13331
+ TableRow2,
13332
+ {
13333
+ ref: setNodeRef,
13334
+ style,
13335
+ "data-sortable-id": id,
13336
+ className: cn(highlighted && "bg-blue-50 ring-1 ring-blue-300"),
13337
+ children: [
13338
+ /* @__PURE__ */ jsx(TableCell2, { className: "px-0 py-0.5 w-6", children: /* @__PURE__ */ jsx(
13339
+ "div",
13340
+ {
13341
+ ref: setActivatorNodeRef,
13342
+ className: "cursor-grab active:cursor-grabbing text-muted-foreground hover:text-foreground flex items-center justify-center",
13343
+ title: t("editable_data_table.drag_to_reorder"),
13344
+ ...attributes,
13345
+ ...listeners,
13346
+ children: /* @__PURE__ */ jsx(GripVertical, { className: "h-3.5 w-3.5" })
13347
+ }
13348
+ ) }),
13349
+ children
13350
+ ]
13351
+ }
13352
+ );
13353
+ });
13354
+ SortableRow.displayName = "SortableRow";
13355
+ var EditableTableRow = React27__default.memo(function EditableTableRow2({
13356
+ row,
13357
+ rowIndex,
13358
+ rowKey,
13359
+ isHighlighted,
13360
+ columns,
13361
+ activeEditColKey,
13362
+ deleteLabel,
13363
+ onCellCommit,
13364
+ onDeleteRow,
13365
+ onTabOutCell,
13366
+ onEnterDownCell,
13367
+ onPasteCell,
13368
+ onActivated
13369
+ }) {
13370
+ const renderCountRef = useRef(0);
13371
+ renderCountRef.current += 1;
13372
+ return /* @__PURE__ */ jsxs(SortableRow, { id: rowKey, highlighted: isHighlighted, children: [
13373
+ columns.map((col, colIndex) => {
13374
+ const resolvedOptions = typeof col.options === "function" ? col.options(row) : col.options;
13375
+ return /* @__PURE__ */ jsx(
13376
+ TableCell2,
13377
+ {
13378
+ className: cn("px-1 py-0.5", col.width),
13379
+ "data-row-render-count": colIndex === 0 ? renderCountRef.current : void 0,
13380
+ children: /* @__PURE__ */ jsx(
13381
+ EditableCell,
13382
+ {
13383
+ value: row[col.key],
13384
+ type: col.type ?? "text",
13385
+ options: resolvedOptions,
13386
+ defaultColorValue: col.defaultValue?.(rowIndex),
13387
+ clearable: col.clearable,
13388
+ editable: col.editable !== false,
13389
+ forceEdit: activeEditColKey === col.key,
13390
+ onTabOut: (direction) => onTabOutCell(rowIndex, col.key, direction),
13391
+ onEnterDown: () => onEnterDownCell(rowIndex, col.key),
13392
+ onPaste: (text) => onPasteCell(rowIndex, col.key, text),
13393
+ onActivated,
13394
+ onCommit: (v) => onCellCommit(rowIndex, col.key, v)
13395
+ }
13396
+ )
13397
+ },
13398
+ col.key
13399
+ );
13400
+ }),
13401
+ /* @__PURE__ */ jsx(TableCell2, { className: "px-0 py-0.5 w-8", children: /* @__PURE__ */ jsx(
13402
+ Button,
13403
+ {
13404
+ variant: "ghost",
13405
+ size: "icon",
13406
+ className: "h-7 w-7 text-muted-foreground hover:text-destructive",
13407
+ onClick: () => onDeleteRow(rowIndex),
13408
+ "aria-label": deleteLabel,
13409
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3 w-3" })
13410
+ }
13411
+ ) })
13412
+ ] });
13413
+ });
13414
+ EditableTableRow.displayName = "EditableTableRow";
13415
+ function EditableDataTable({
13416
+ columns,
13417
+ rows,
13418
+ onChange,
13419
+ getRowKey,
13420
+ createRow,
13421
+ highlightedKey,
13422
+ className,
13423
+ label
13424
+ }) {
13425
+ const t = useHazoUiT();
13426
+ const editableColumns = useMemo(
13427
+ () => columns.filter(
13428
+ (col) => col.editable !== false && (col.type ?? "text") !== "select" && (col.type ?? "text") !== "color"
13429
+ ).map((col) => col.key),
13430
+ [columns]
13431
+ );
13432
+ const [activeEditCell, setActiveEditCell] = useState(null);
13433
+ const rowsRef = useRef(rows);
13434
+ rowsRef.current = rows;
13435
+ const onChangeRef = useRef(onChange);
13436
+ onChangeRef.current = onChange;
13437
+ const handleTabOut = useCallback(
13438
+ (rowIndex, colKey, direction) => {
13439
+ const idx = editableColumns.indexOf(colKey);
13440
+ if (idx === -1) return false;
13441
+ if (direction === 1) {
13442
+ if (idx < editableColumns.length - 1) {
13443
+ setActiveEditCell({ row: rowIndex, col: editableColumns[idx + 1] });
13444
+ return true;
13445
+ }
13446
+ if (rowIndex + 1 < rows.length) {
13447
+ setActiveEditCell({ row: rowIndex + 1, col: editableColumns[0] });
13448
+ return true;
13449
+ }
13450
+ return false;
13451
+ }
13452
+ if (idx > 0) {
13453
+ setActiveEditCell({ row: rowIndex, col: editableColumns[idx - 1] });
13454
+ return true;
13455
+ }
13456
+ if (rowIndex - 1 >= 0) {
13457
+ setActiveEditCell({
13458
+ row: rowIndex - 1,
13459
+ col: editableColumns[editableColumns.length - 1]
13460
+ });
13461
+ return true;
13462
+ }
13463
+ return false;
13464
+ },
13465
+ [editableColumns, rows.length]
13466
+ );
13467
+ const handleEnterDown = useCallback(
13468
+ (rowIndex, colKey) => {
13469
+ if (rowIndex + 1 < rows.length) {
13470
+ setActiveEditCell({ row: rowIndex + 1, col: colKey });
13471
+ return true;
13472
+ }
13473
+ return false;
13474
+ },
13475
+ [rows.length]
13476
+ );
13477
+ const handlePaste = useCallback(
13478
+ (rowIndex, colKey, text) => {
13479
+ const startColIdx = editableColumns.indexOf(
13480
+ colKey
13481
+ );
13482
+ if (startColIdx === -1) return false;
13483
+ const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
13484
+ const lines = normalized.split("\n");
13485
+ if (lines.length > 1 && lines[lines.length - 1] === "") {
13486
+ lines.pop();
13487
+ }
13488
+ const hasTab = normalized.includes(" ");
13489
+ const delimiter = hasTab ? " " : ",";
13490
+ const grid = lines.map((line) => line.split(delimiter));
13491
+ const isSingleCell = grid.length === 1 && grid[0].length === 1;
13492
+ if (isSingleCell) return false;
13493
+ const currentRows = rowsRef.current;
13494
+ const nextRows = currentRows.slice();
13495
+ grid.forEach((lineCells, rowOffset) => {
13496
+ const targetRowIndex = rowIndex + rowOffset;
13497
+ while (targetRowIndex >= nextRows.length) {
13498
+ nextRows.push(createRow());
13499
+ }
13500
+ const targetRow = {
13501
+ ...nextRows[targetRowIndex]
13502
+ };
13503
+ lineCells.forEach((cellValue, colOffset) => {
13504
+ const targetColIdx = startColIdx + colOffset;
13505
+ if (targetColIdx >= editableColumns.length) return;
13506
+ const targetColKey = editableColumns[targetColIdx];
13507
+ const col = columns.find((c) => c.key === targetColKey);
13508
+ if (col?.type === "number") {
13509
+ const n = Number(cellValue);
13510
+ targetRow[targetColKey] = Number.isNaN(n) || cellValue.trim() === "" ? 0 : n;
13511
+ } else {
13512
+ targetRow[targetColKey] = cellValue;
13513
+ }
13514
+ });
13515
+ nextRows[targetRowIndex] = targetRow;
13516
+ });
13517
+ onChangeRef.current(nextRows);
13518
+ return true;
13519
+ },
13520
+ [editableColumns, columns, createRow]
13521
+ );
13522
+ const handleCellChange = useCallback(
13523
+ (rowIndex, colKey, value) => {
13524
+ const currentRows = rowsRef.current;
13525
+ const next = currentRows.map(
13526
+ (row, i) => i === rowIndex ? { ...row, [colKey]: value } : row
13527
+ );
13528
+ onChangeRef.current(next);
13529
+ },
13530
+ []
13531
+ );
13532
+ const handleDelete = useCallback((rowIndex) => {
13533
+ const currentRows = rowsRef.current;
13534
+ onChangeRef.current(currentRows.filter((_, i) => i !== rowIndex));
13535
+ }, []);
13536
+ const onCellActivated = useCallback(() => setActiveEditCell(null), []);
13537
+ const handleAdd = () => {
13538
+ onChange([...rows, createRow()]);
13539
+ };
13540
+ const sensors = useSensors(
13541
+ useSensor(PointerSensor),
13542
+ useSensor(KeyboardSensor, {
13543
+ coordinateGetter: sortableKeyboardCoordinates
13544
+ })
13545
+ );
13546
+ const rowKeys = useMemo(
13547
+ () => rows.map((row, i) => getRowKey(row, i)),
13548
+ [rows, getRowKey]
13549
+ );
13550
+ const handleDragEnd = useCallback(
13551
+ (event) => {
13552
+ const { active, over } = event;
13553
+ if (!over || active.id === over.id) return;
13554
+ const oldIndex = rows.findIndex(
13555
+ (row, i) => getRowKey(row, i) === active.id
13556
+ );
13557
+ const newIndex = rows.findIndex(
13558
+ (row, i) => getRowKey(row, i) === over.id
13559
+ );
13560
+ if (oldIndex === -1 || newIndex === -1) return;
13561
+ onChange(arrayMove(rows, oldIndex, newIndex));
13562
+ },
13563
+ [rows, getRowKey, onChange]
13564
+ );
13565
+ return (
13566
+ // DndContext wraps the whole component rather than just <TableBody> —
13567
+ // it renders its own hidden accessibility helpers (RestoreFocus,
13568
+ // live-region announcer) as DOM siblings of its children. Nesting it
13569
+ // directly inside <tbody> would make those helpers invalid children of
13570
+ // <tbody> (only <tr> is valid there); wrapping the outer <div> instead
13571
+ // gives them a valid home. <SortableContext> is a plain context
13572
+ // provider with no DOM output of its own, so it can stay right inside
13573
+ // <TableBody>, wrapping only the actual <tr> rows.
13574
+ /* @__PURE__ */ jsx(
13575
+ DndContext,
13576
+ {
13577
+ sensors,
13578
+ collisionDetection: closestCenter,
13579
+ onDragEnd: handleDragEnd,
13580
+ children: /* @__PURE__ */ jsxs("div", { "data-editable-data-table": true, className: cn("flex flex-col gap-1", className), children: [
13581
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-1", children: [
13582
+ label && /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-muted-foreground uppercase tracking-wide", children: label }),
13583
+ /* @__PURE__ */ jsxs(
13584
+ Button,
13585
+ {
13586
+ variant: "ghost",
13587
+ size: "sm",
13588
+ className: "h-6 px-2 text-xs ml-auto",
13589
+ onClick: handleAdd,
13590
+ "aria-label": t("editable_data_table.add"),
13591
+ children: [
13592
+ /* @__PURE__ */ jsx(Plus, { className: "h-3 w-3 mr-1" }),
13593
+ t("editable_data_table.add")
13594
+ ]
13595
+ }
13596
+ )
13597
+ ] }),
13598
+ /* @__PURE__ */ jsxs(Table2, { children: [
13599
+ /* @__PURE__ */ jsx(TableHeader2, { children: /* @__PURE__ */ jsxs(TableRow2, { className: "bg-muted/60 hover:bg-muted/60 border-b-2 border-border", children: [
13600
+ /* @__PURE__ */ jsx(TableHead, { className: "w-6 px-0" }),
13601
+ columns.map((col) => /* @__PURE__ */ jsx(
13602
+ TableHead,
13603
+ {
13604
+ className: cn(
13605
+ "text-[11px] font-semibold uppercase tracking-wide text-foreground px-1 py-1 h-8",
13606
+ col.width
13607
+ ),
13608
+ children: col.label
13609
+ },
13610
+ col.key
13611
+ )),
13612
+ /* @__PURE__ */ jsx(TableHead, { className: "w-8 px-0" })
13613
+ ] }) }),
13614
+ /* @__PURE__ */ jsxs(TableBody, { children: [
13615
+ rows.length === 0 && /* @__PURE__ */ jsx(TableRow2, { children: /* @__PURE__ */ jsx(
13616
+ TableCell2,
13617
+ {
13618
+ colSpan: columns.length + 2,
13619
+ className: "text-center text-xs text-muted-foreground italic py-4",
13620
+ children: t("editable_data_table.empty")
13621
+ }
13622
+ ) }),
13623
+ /* @__PURE__ */ jsx(
13624
+ SortableContext,
13625
+ {
13626
+ items: rowKeys,
13627
+ strategy: verticalListSortingStrategy,
13628
+ children: rows.map((row, rowIndex) => {
13629
+ const key = rowKeys[rowIndex];
13630
+ const isHighlighted = highlightedKey != null && key === highlightedKey;
13631
+ const activeEditColKey = activeEditCell?.row === rowIndex ? activeEditCell.col : null;
13632
+ return /* @__PURE__ */ jsx(
13633
+ EditableTableRow,
13634
+ {
13635
+ row,
13636
+ rowIndex,
13637
+ rowKey: key,
13638
+ isHighlighted,
13639
+ columns,
13640
+ activeEditColKey,
13641
+ deleteLabel: t("editable_data_table.delete"),
13642
+ onCellCommit: handleCellChange,
13643
+ onDeleteRow: handleDelete,
13644
+ onTabOutCell: handleTabOut,
13645
+ onEnterDownCell: handleEnterDown,
13646
+ onPasteCell: handlePaste,
13647
+ onActivated: onCellActivated
13648
+ },
13649
+ key
13650
+ );
13651
+ })
13652
+ }
13653
+ )
13654
+ ] })
13655
+ ] })
13656
+ ] })
13657
+ }
13658
+ )
13659
+ );
13660
+ }
13661
+ function TrendDeltaBadge({ pct }) {
13662
+ if (pct === null) {
13663
+ return /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "\u2014" });
13664
+ }
13665
+ const up = pct >= 0;
13666
+ const cls = up ? "text-emerald-600" : "text-red-600";
13667
+ const arrow = up ? "\u25B2" : "\u25BC";
13668
+ return /* @__PURE__ */ jsxs("span", { className: `text-xs font-medium ${cls}`, title: "vs previous period", children: [
13669
+ arrow,
13670
+ " ",
13671
+ up ? "+" : "",
13672
+ pct,
13673
+ "%"
13674
+ ] });
13675
+ }
13676
+ var CARD_HEIGHT_PX = 168;
13677
+ function TrendStatCard({
13678
+ w,
13679
+ totalLabel,
13680
+ prevTemplate
13681
+ }) {
13682
+ return /* @__PURE__ */ jsxs(
13683
+ "div",
13684
+ {
13685
+ className: "cls_trend_stat_card flex min-w-0 flex-col justify-between rounded-lg border bg-card p-4",
13686
+ style: { height: CARD_HEIGHT_PX },
13687
+ children: [
13688
+ /* @__PURE__ */ jsx("div", { className: "truncate text-xs text-muted-foreground", children: w.label }),
13689
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between gap-1", children: [
13690
+ /* @__PURE__ */ jsx("span", { className: "text-3xl font-bold tabular-nums", children: w.count.toLocaleString() }),
13691
+ w.key !== "all" && /* @__PURE__ */ jsx(TrendDeltaBadge, { pct: w.pctChange })
13692
+ ] }),
13693
+ w.key !== "all" && w.prevCount !== null && /* @__PURE__ */ jsx("div", { className: "truncate text-xs text-muted-foreground", children: prevTemplate.replace("{count}", w.prevCount.toLocaleString()) }),
13694
+ w.key === "all" && /* @__PURE__ */ jsx("div", { className: "truncate text-xs text-muted-foreground", children: totalLabel })
13695
+ ]
13696
+ }
13697
+ );
13698
+ }
13699
+ function niceMax(n) {
13700
+ if (n <= 0) return 1;
13701
+ const exp = Math.floor(Math.log10(n));
13702
+ const base = Math.pow(10, exp);
13703
+ const frac = n / base;
13704
+ let niceFrac;
13705
+ if (frac <= 1) niceFrac = 1;
13706
+ else if (frac <= 2) niceFrac = 2;
13707
+ else if (frac <= 5) niceFrac = 5;
13708
+ else niceFrac = 10;
13709
+ return niceFrac * base;
13710
+ }
13711
+ function TrendMiniChart({
13712
+ data,
13713
+ title,
13714
+ tooltipSuffix
13715
+ }) {
13716
+ const [hoverIndex, setHoverIndex] = useState(null);
13717
+ const total = data.reduce((n2, d) => n2 + d.count, 0);
13718
+ const rawMax = Math.max(1, ...data.map((d) => d.count));
13719
+ const max = niceMax(rawMax);
13720
+ const mid = Math.round(max / 2);
13721
+ const W = 480;
13722
+ const H = 120;
13723
+ const marginLeft = 34;
13724
+ const marginRight = 8;
13725
+ const marginTop = 16;
13726
+ const marginBottom = 20;
13727
+ const plotW = W - marginLeft - marginRight;
13728
+ const plotH = H - marginTop - marginBottom;
13729
+ const gap = 6;
13730
+ const n = Math.max(1, data.length);
13731
+ const colW = plotW / n;
13732
+ const bw = Math.max(1, colW - gap);
13733
+ const yFor = (value) => marginTop + plotH - value / max * plotH;
13734
+ const hovered = hoverIndex !== null ? data[hoverIndex] : null;
13735
+ return /* @__PURE__ */ jsxs(
13736
+ "div",
13737
+ {
13738
+ className: "cls_trend_mini_chart col-span-6 flex min-w-0 flex-col rounded-lg border bg-card p-3",
13739
+ style: { height: CARD_HEIGHT_PX },
13740
+ children: [
13741
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-baseline justify-between", children: [
13742
+ /* @__PURE__ */ jsx("div", { className: "truncate text-[11px] text-muted-foreground", children: title }),
13743
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold tabular-nums", children: total.toLocaleString() })
13744
+ ] }),
13745
+ /* @__PURE__ */ jsxs("div", { className: "relative mt-2 min-h-0 flex-1", children: [
13746
+ /* @__PURE__ */ jsxs(
13747
+ "svg",
13748
+ {
13749
+ role: "img",
13750
+ "aria-label": data.map((d) => `${d.label} ${d.count}`).join(", "),
13751
+ viewBox: `0 0 ${W} ${H}`,
13752
+ preserveAspectRatio: "none",
13753
+ className: "h-full w-full",
13754
+ children: [
13755
+ [0, mid, max].map((tick) => {
13756
+ const y = yFor(tick);
13757
+ return /* @__PURE__ */ jsxs("g", { children: [
13758
+ /* @__PURE__ */ jsx(
13759
+ "line",
13760
+ {
13761
+ x1: marginLeft,
13762
+ x2: W - marginRight,
13763
+ y1: y,
13764
+ y2: y,
13765
+ stroke: "currentColor",
13766
+ strokeOpacity: 0.1
13767
+ }
13768
+ ),
13769
+ /* @__PURE__ */ jsx(
13770
+ "text",
13771
+ {
13772
+ x: marginLeft - 6,
13773
+ y,
13774
+ dy: 3,
13775
+ textAnchor: "end",
13776
+ fontSize: 9,
13777
+ className: "fill-muted-foreground",
13778
+ children: tick.toLocaleString()
13779
+ }
13780
+ )
13781
+ ] }, tick);
13782
+ }),
13783
+ /* @__PURE__ */ jsx(
13784
+ "line",
13785
+ {
13786
+ x1: marginLeft,
13787
+ x2: W - marginRight,
13788
+ y1: marginTop + plotH,
13789
+ y2: marginTop + plotH,
13790
+ stroke: "currentColor",
13791
+ strokeOpacity: 0.2
13792
+ }
13793
+ ),
13794
+ data.map((d, i) => {
13795
+ const barH = Math.round(d.count / max * plotH);
13796
+ const colX = marginLeft + i * colW;
13797
+ const barX = colX + (colW - bw) / 2;
13798
+ const barY = marginTop + plotH - barH;
13799
+ const centerX = colX + colW / 2;
13800
+ const isDimmed = hoverIndex !== null && hoverIndex !== i;
13801
+ return /* @__PURE__ */ jsxs(
13802
+ "g",
13803
+ {
13804
+ onMouseEnter: () => setHoverIndex(i),
13805
+ onMouseLeave: () => setHoverIndex(null),
13806
+ children: [
13807
+ /* @__PURE__ */ jsx(
13808
+ "rect",
13809
+ {
13810
+ x: colX,
13811
+ y: marginTop,
13812
+ width: colW,
13813
+ height: plotH,
13814
+ fill: "transparent"
13815
+ }
13816
+ ),
13817
+ /* @__PURE__ */ jsx(
13818
+ "rect",
13819
+ {
13820
+ x: barX,
13821
+ y: barY,
13822
+ width: bw,
13823
+ height: Math.max(0, barH),
13824
+ rx: 2,
13825
+ className: "fill-primary",
13826
+ fillOpacity: isDimmed ? 0.45 : 1,
13827
+ children: /* @__PURE__ */ jsx("title", { children: `${d.label}: ${d.count} ${tooltipSuffix}` })
13828
+ }
13829
+ ),
13830
+ /* @__PURE__ */ jsx(
13831
+ "text",
13832
+ {
13833
+ x: centerX,
13834
+ y: barY - 4,
13835
+ textAnchor: "middle",
13836
+ fontSize: 10,
13837
+ fontWeight: 600,
13838
+ className: "fill-foreground",
13839
+ children: d.count
13840
+ }
13841
+ ),
13842
+ /* @__PURE__ */ jsx(
13843
+ "text",
13844
+ {
13845
+ x: centerX,
13846
+ y: marginTop + plotH + 14,
13847
+ textAnchor: "middle",
13848
+ fontSize: 9,
13849
+ className: "fill-muted-foreground",
13850
+ children: d.label
13851
+ }
13852
+ )
13853
+ ]
13854
+ },
13855
+ d.label
13856
+ );
13857
+ })
13858
+ ]
13859
+ }
13860
+ ),
13861
+ hovered && hoverIndex !== null && /* @__PURE__ */ jsxs(
13862
+ "div",
13863
+ {
13864
+ className: "pointer-events-none absolute z-10 -translate-x-1/2 -translate-y-full rounded-md border bg-popover px-2 py-1 text-[10px] font-medium text-popover-foreground shadow-sm",
13865
+ style: {
13866
+ left: `${Math.min(
13867
+ 92,
13868
+ Math.max(
13869
+ 8,
13870
+ (marginLeft + hoverIndex * (plotW / n) + plotW / n / 2) / W * 100
13871
+ )
13872
+ )}%`,
13873
+ top: `${(marginTop + plotH - hovered.count / max * plotH) / H * 100}%`
13874
+ },
13875
+ children: [
13876
+ hovered.label,
13877
+ ": ",
13878
+ hovered.count,
13879
+ " ",
13880
+ tooltipSuffix
13881
+ ]
13882
+ }
13883
+ )
13884
+ ] })
13885
+ ]
13886
+ }
13887
+ );
13888
+ }
13889
+ function TrendSummary({
13890
+ windows,
13891
+ chartData,
13892
+ chartTitle = "Activity (YTD)",
13893
+ chartTooltipSuffix = "",
13894
+ totalLabel = "total",
13895
+ prevTemplate = "prev {count}",
13896
+ className
13897
+ }) {
13898
+ return /* @__PURE__ */ jsxs(
13899
+ "div",
13900
+ {
13901
+ className: cn(
13902
+ "cls_trend_summary mb-4 grid grid-cols-6 items-start gap-2 md:grid-cols-12",
13903
+ className
13904
+ ),
13905
+ children: [
13906
+ windows.map((w) => /* @__PURE__ */ jsx(
13907
+ TrendStatCard,
13908
+ {
13909
+ w,
13910
+ totalLabel,
13911
+ prevTemplate
13912
+ },
13913
+ w.key
13914
+ )),
13915
+ chartData && chartData.length > 0 && /* @__PURE__ */ jsx(
13916
+ TrendMiniChart,
13917
+ {
13918
+ data: chartData,
13919
+ title: chartTitle,
13920
+ tooltipSuffix: chartTooltipSuffix
13921
+ }
13922
+ )
13923
+ ]
13924
+ }
13925
+ );
13926
+ }
13927
+
13928
+ // src/lib/trend_stats.ts
13929
+ var DAY_MS = 864e5;
13930
+ var MONTHS = [
13931
+ "Jan",
13932
+ "Feb",
13933
+ "Mar",
13934
+ "Apr",
13935
+ "May",
13936
+ "Jun",
13937
+ "Jul",
13938
+ "Aug",
13939
+ "Sep",
13940
+ "Oct",
13941
+ "Nov",
13942
+ "Dec"
13943
+ ];
13944
+ function pctOf(count, prev) {
13945
+ if (prev === null || prev === 0) return null;
13946
+ return Math.round((count - prev) / prev * 100);
13947
+ }
13948
+ var DEFAULT_WINDOWS = [
13949
+ { key: "7d", label: "7 days", days: 7 },
13950
+ { key: "30d", label: "30 days", days: 30 },
13951
+ { key: "90d", label: "3 months", days: 90 },
13952
+ { key: "180d", label: "6 months", days: 180 }
13953
+ ];
13954
+ function computeTrendStats(items, now = /* @__PURE__ */ new Date(), options) {
13955
+ const nowMs = now.getTime();
13956
+ const times = [];
13957
+ for (const item of items) {
13958
+ if (!item || !item.created_at) continue;
13959
+ const t = new Date(item.created_at).getTime();
13960
+ if (!Number.isNaN(t)) times.push(t);
13961
+ }
13962
+ const countBetween = (startMs, endMs) => times.reduce((n, t) => t >= startMs && t < endMs ? n + 1 : n, 0);
13963
+ const total = times.length;
13964
+ const rollingDefs = options?.windows ?? DEFAULT_WINDOWS;
13965
+ const windows = [
13966
+ { key: "all", label: options?.totalLabel ?? "All time", count: total, prevCount: null, pctChange: null }
13967
+ ];
13968
+ for (const w of rollingDefs) {
13969
+ const span = w.days * DAY_MS;
13970
+ const count = countBetween(nowMs - span, nowMs);
13971
+ const prevCount = countBetween(nowMs - 2 * span, nowMs - span);
13972
+ windows.push({
13973
+ key: w.key,
13974
+ label: w.label,
13975
+ count,
13976
+ prevCount,
13977
+ pctChange: pctOf(count, prevCount)
13978
+ });
13979
+ }
13980
+ const ytdStart = new Date(now.getFullYear(), 0, 1).getTime();
13981
+ const elapsed = nowMs - ytdStart;
13982
+ const ytdCount = countBetween(ytdStart, nowMs);
13983
+ const prevYtdStart = new Date(now.getFullYear() - 1, 0, 1).getTime();
13984
+ const prevYtdCount = countBetween(prevYtdStart, prevYtdStart + elapsed);
13985
+ windows.push({
13986
+ key: "ytd",
13987
+ label: "YTD",
13988
+ count: ytdCount,
13989
+ prevCount: prevYtdCount,
13990
+ pctChange: pctOf(ytdCount, prevYtdCount)
13991
+ });
13992
+ const ytdMonthly = [];
13993
+ const year = now.getFullYear();
13994
+ for (let m = 0; m <= now.getMonth(); m++) {
13995
+ const monthStart = new Date(year, m, 1).getTime();
13996
+ const monthEnd = new Date(year, m + 1, 1).getTime();
13997
+ ytdMonthly.push({ label: MONTHS[m], count: countBetween(monthStart, monthEnd) });
13998
+ }
13999
+ return { total, windows, ytdMonthly };
14000
+ }
13157
14001
 
13158
- export { ANIMATION_PRESETS, Accordion, AccordionContent, AccordionItem, AccordionTrigger, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AppFooter, AppHeader, Badge, Button, ButtonGroup, ButtonGroupSeparator, ButtonGroupText, CELEBRATION_GRADIENT, Calendar, CanvasTextToolbar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CelebrationProvider, Checkbox, Collapsible, CollapsibleContent2 as CollapsibleContent, CollapsibleTrigger2 as CollapsibleTrigger, CommandNodeExtension, CommandPill, CommandPopover, DEFAULT_ANNOUNCE_EVENT, DateRangeSelector, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, ErrorBanner, ErrorPage, FunnelChart, HazoBrowserTab, HazoContextProvider, HazoUiConfirmDialog, HazoUiDialog, DialogClose as HazoUiDialogClose, DialogContent as HazoUiDialogContent, DialogDescription as HazoUiDialogDescription, DialogFooter as HazoUiDialogFooter, DialogHeader as HazoUiDialogHeader, DialogOverlay as HazoUiDialogOverlay, DialogPortal as HazoUiDialogPortal, Dialog as HazoUiDialogRoot, DialogTitle as HazoUiDialogTitle, DialogTrigger as HazoUiDialogTrigger, HazoUiEtaProgress, HazoUiFlexInput, HazoUiFlexRadio, HazoUiI18nProvider, HazoUiIcon, HazoUiIconPicker, HazoUiImageCropper, HazoUiImageCropperDialog, HazoUiKanban, HazoUiKanbanFilter, HazoUiMemoryDropdown, HazoUiMultiFilterDialog, HazoUiMultiSortDialog, HazoUiPillRadio, HazoUiProgressBar, HazoUiRte, HazoUiTable, HazoUiTextarea, HazoUiTextbox, HazoUiToaster, HoverCard, HoverCardContent, HoverCardTrigger, Input, InputAffix, InverseSparkline, Label3 as Label, LiveRegion, LoadingTimeout, LocalePicker, Logo, MarkdownEditor, NotificationCountBadge, NotificationItem, NotificationPanel, Popover, PopoverContent, PopoverTrigger, ProgressiveImage, RadioGroup, RadioGroupItem, ScrollArea, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator3 as Separator, Command as ShadcnCommand, CommandEmpty as ShadcnCommandEmpty, CommandGroup as ShadcnCommandGroup, CommandInput as ShadcnCommandInput, CommandItem as ShadcnCommandItem, CommandList as ShadcnCommandList, Skeleton, SkeletonBar, SkeletonCircle, SkeletonGroup, SkeletonRect, Slider, Sparkline, Spinner, StackedBars, StatusBadge, Switch, Table2 as Table, TableBody, TableCaption, TableCell2 as TableCell, TableFooter, TableHead, TableHeader2 as TableHeader, TableRow2 as TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, announce, applyKanbanFilter, buttonGroupVariants, celebrate, cn, computeEta, create_command_suggestion_extension, defaultHazoUiT, easeToward, errorToast, format_num, generateUUID, get_hazo_ui_config, get_logger, hasUrlParam, isLucideValue, makeStatusBadge, median, parse_commands_from_text, permissionToast, pick_x_label_indices, readRememberedState, reset_hazo_ui_config, resolve_animation_classes, safeHref, set_hazo_ui_config, set_logger, successToast, text_to_tiptap_content, toggleVariants, upsertEntry as upsertMemoryEntry, useClickOutside, useCopyToClipboard, useDebounce, useErrorDisplay, useEtaProgress, useFullscreen, useHazoEvent, useHazoUiT, useIsMobile, useLoadingState, useLocalStorage, useLongPress, useMediaQuery, useRememberedState, useScrollRestore, useSessionStorage, useViewport, useWakeLock, use_fullscreen, use_wake_lock, writeRememberedState };
14002
+ export { ANIMATION_PRESETS, Accordion, AccordionContent, AccordionItem, AccordionTrigger, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AppFooter, AppHeader, Badge, Button, ButtonGroup, ButtonGroupSeparator, ButtonGroupText, CELEBRATION_GRADIENT, Calendar, CanvasTextToolbar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CelebrationProvider, Checkbox, Collapsible, CollapsibleContent2 as CollapsibleContent, CollapsibleTrigger2 as CollapsibleTrigger, CommandNodeExtension, CommandPill, CommandPopover, DEFAULT_ANNOUNCE_EVENT, DateRangeSelector, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EditableDataTable, EmptyState, ErrorBanner, ErrorPage, FunnelChart, HazoBrowserTab, HazoContextProvider, HazoUiConfirmDialog, HazoUiDialog, DialogClose as HazoUiDialogClose, DialogContent as HazoUiDialogContent, DialogDescription as HazoUiDialogDescription, DialogFooter as HazoUiDialogFooter, DialogHeader as HazoUiDialogHeader, DialogOverlay as HazoUiDialogOverlay, DialogPortal as HazoUiDialogPortal, Dialog as HazoUiDialogRoot, DialogTitle as HazoUiDialogTitle, DialogTrigger as HazoUiDialogTrigger, HazoUiEtaProgress, HazoUiFlexInput, HazoUiFlexRadio, HazoUiI18nProvider, HazoUiIcon, HazoUiIconPicker, HazoUiImageCropper, HazoUiImageCropperDialog, HazoUiKanban, HazoUiKanbanFilter, HazoUiMemoryDropdown, HazoUiMultiFilterDialog, HazoUiMultiSortDialog, HazoUiPillRadio, HazoUiProgressBar, HazoUiRte, HazoUiTable, HazoUiTextarea, HazoUiTextbox, HazoUiToaster, HoverCard, HoverCardContent, HoverCardTrigger, Input, InputAffix, InverseSparkline, Label3 as Label, LiveRegion, LoadingTimeout, LocalePicker, Logo, MarkdownEditor, NotificationCountBadge, NotificationItem, NotificationPanel, Popover, PopoverContent, PopoverTrigger, ProgressiveImage, RadioGroup, RadioGroupItem, ScrollArea, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator3 as Separator, Command as ShadcnCommand, CommandEmpty as ShadcnCommandEmpty, CommandGroup as ShadcnCommandGroup, CommandInput as ShadcnCommandInput, CommandItem as ShadcnCommandItem, CommandList as ShadcnCommandList, Skeleton, SkeletonBar, SkeletonCircle, SkeletonGroup, SkeletonRect, Slider, Sparkline, Spinner, StackedBars, StatusBadge, Switch, Table2 as Table, TableBody, TableCaption, TableCell2 as TableCell, TableFooter, TableHead, TableHeader2 as TableHeader, TableRow2 as TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, TrendSummary, announce, applyKanbanFilter, buttonGroupVariants, celebrate, cn, computeEta, computeTrendStats, create_command_suggestion_extension, defaultHazoUiT, easeToward, errorToast, format_num, generateUUID, get_hazo_ui_config, get_logger, hasUrlParam, isLucideValue, makeStatusBadge, median, parse_commands_from_text, permissionToast, pick_x_label_indices, readRememberedState, reset_hazo_ui_config, resolve_animation_classes, safeHref, set_hazo_ui_config, set_logger, successToast, text_to_tiptap_content, toggleVariants, upsertEntry as upsertMemoryEntry, useClickOutside, useCopyToClipboard, useDebounce, useErrorDisplay, useEtaProgress, useFullscreen, useHazoEvent, useHazoUiT, useIsMobile, useLoadingState, useLocalStorage, useLongPress, useMediaQuery, useRememberedState, useScrollRestore, useSessionStorage, useViewport, useWakeLock, use_fullscreen, use_wake_lock, writeRememberedState };
13159
14003
  //# sourceMappingURL=index.js.map
13160
14004
  //# sourceMappingURL=index.js.map