@ryuzaki13/react-foundation-lib 1.1.8 → 1.1.10

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.
@@ -21,6 +21,18 @@ export type DownloadExcelFileArgs = {
21
21
  columnIndex: number;
22
22
  }) => ExcelCellStyle | undefined;
23
23
  };
24
+ /** Описание одного табличного листа внутри Excel-книги. */
25
+ export type ExcelWorkbookSheet = Omit<DownloadExcelFileArgs, "fileName"> & {
26
+ /** Желаемое имя листа; writer нормализует ограничения формата и конфликты имён. */
27
+ name: string;
28
+ };
29
+ /** Аргументы формирования Excel-книги с одним или несколькими табличными листами. */
30
+ export type DownloadExcelWorkbookArgs = {
31
+ /** Имя скачиваемого `.xlsx` файла. */
32
+ fileName: string;
33
+ /** Листы книги в порядке отображения в Excel. */
34
+ sheets: ExcelWorkbookSheet[];
35
+ };
24
36
  /**
25
37
  * Приводит общий value-state проекта к базовому Excel-стилю.
26
38
  *
@@ -35,6 +47,14 @@ export declare function resolveExcelCellStyleFromState(state: State | undefined)
35
47
  * невалидный `<autoFilter>` без реального диапазона.
36
48
  */
37
49
  export declare function buildExcelAutoFilterRef(columnCount: number, rowCount: number): string | undefined;
50
+ /**
51
+ * Нормализует имена листов по ограничениям Excel и делает их уникальными.
52
+ *
53
+ * Runtime-заголовки могут содержать запрещённые символы, превышать 31 знак или
54
+ * совпадать между несколькими представлениями одной конфигурации. Нормализация
55
+ * выполняется централизованно, чтобы все consumers получали открываемую книгу.
56
+ */
57
+ export declare function resolveExcelSheetNames(names: readonly string[]): string[];
38
58
  /**
39
59
  * Собирает SheetData для `write-excel-file`.
40
60
  *
@@ -42,6 +62,13 @@ export declare function buildExcelAutoFilterRef(columnCount: number, rowCount: n
42
62
  * колонки/строки и необязательный callback стилей конкретных ячеек.
43
63
  */
44
64
  export declare function buildExcelSheetData(args: Omit<DownloadExcelFileArgs, "fileName" | "autoFilter">): SheetData;
65
+ /**
66
+ * Формирует и скачивает Excel-книгу с независимыми табличными листами.
67
+ *
68
+ * Каждый лист получает собственные ширины колонок, стили ячеек и диапазон
69
+ * автофильтра. Доменная подготовка колонок и строк остаётся у вызывающей стороны.
70
+ */
71
+ export declare function downloadExcelWorkbook(args: DownloadExcelWorkbookArgs): Promise<void>;
45
72
  /**
46
73
  * Формирует и скачивает `.xlsx` в браузере.
47
74
  *
@@ -1 +1 @@
1
- {"version":3,"file":"excel.d.ts","sourceRoot":"","sources":["../../src/excel/excel.ts"],"names":[],"mappings":"AAAA,OAAsB,EAAa,KAAK,UAAU,EAAgB,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAGnH,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,UAAU,CAAC;AAEtC,MAAM,MAAM,WAAW,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,iBAAiB,GAAG,iBAAiB,GAAG,eAAe,GAAG,kBAAkB,CAAC;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE/C,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,UAAU,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC,CAAC;AAE3E,MAAM,MAAM,qBAAqB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,GAAG,EAAE,QAAQ,CAAC;QAAC,MAAM,EAAE,WAAW,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,KAAK,cAAc,GAAG,SAAS,CAAC;CACnI,CAAC;AAiBF;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,GAAG,cAAc,GAAG,SAAS,CAGnG;AAoBD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAIjG;AA2CD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,IAAI,CAAC,qBAAqB,EAAE,UAAU,GAAG,YAAY,CAAC,GAAG,SAAS,CA8B3G;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBlF"}
1
+ {"version":3,"file":"excel.d.ts","sourceRoot":"","sources":["../../src/excel/excel.ts"],"names":[],"mappings":"AAAA,OAAsB,EAAa,KAAK,UAAU,EAA4B,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAG/H,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,UAAU,CAAC;AAEtC,MAAM,MAAM,WAAW,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,iBAAiB,GAAG,iBAAiB,GAAG,eAAe,GAAG,kBAAkB,CAAC;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE/C,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,UAAU,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC,CAAC;AAE3E,MAAM,MAAM,qBAAqB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,GAAG,EAAE,QAAQ,CAAC;QAAC,MAAM,EAAE,WAAW,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,KAAK,cAAc,GAAG,SAAS,CAAC;CACnI,CAAC;AAEF,2DAA2D;AAC3D,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,qBAAqB,EAAE,UAAU,CAAC,GAAG;IAC1E,mFAAmF;IACnF,IAAI,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,qFAAqF;AACrF,MAAM,MAAM,yBAAyB,GAAG;IACvC,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,MAAM,EAAE,kBAAkB,EAAE,CAAC;CAC7B,CAAC;AAsBF;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,GAAG,cAAc,GAAG,SAAS,CAGnG;AAoBD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAIjG;AAyBD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CA2BzE;AAuBD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,IAAI,CAAC,qBAAqB,EAAE,UAAU,GAAG,YAAY,CAAC,GAAG,SAAS,CA8B3G;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,yBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwB1F;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAalF"}
@@ -12,12 +12,12 @@ var u = {
12
12
  fontWeight: "bold",
13
13
  bottomBorderStyle: "thin",
14
14
  bottomBorderColor: "#D9D9D9"
15
- };
16
- function f(e) {
15
+ }, f = 31, p = /[\u0000-\u001f[\]/\\:*?]+/g, m = /^'+|'+$/g, h = /\s+/g;
16
+ function g(e) {
17
17
  let t = e ? u[e] : void 0;
18
18
  return t ? { textColor: t } : void 0;
19
19
  }
20
- function p(e) {
20
+ function _(e) {
21
21
  let t = e + 1, n = "";
22
22
  for (; t > 0;) {
23
23
  let e = (t - 1) % 26;
@@ -25,13 +25,28 @@ function p(e) {
25
25
  }
26
26
  return n;
27
27
  }
28
- function m(e, t) {
29
- if (!(e <= 0 || t <= 0)) return `A1:${p(e - 1)}${t}`;
30
- }
31
- function h(e) {
32
- if (e) return { files: { transform: { "xl/worksheets/sheet{id}.xml": { transform: (t) => t.replace("</sheetData>", `</sheetData><autoFilter ref="${e}"/>`) } } } };
28
+ function v(e, t) {
29
+ if (!(e <= 0 || t <= 0)) return `A1:${_(e - 1)}${t}`;
30
+ }
31
+ function y(e) {
32
+ if (e.some(Boolean)) return { files: { transform: { "xl/worksheets/sheet{id}.xml": { transform: (t, n, r) => {
33
+ let i = e[r.sheetIndex];
34
+ return i ? t.replace("</sheetData>", `</sheetData><autoFilter ref="${i}"/>`) : t;
35
+ } } } } };
36
+ }
37
+ function b(e) {
38
+ let t = /* @__PURE__ */ new Set(), n = [];
39
+ for (let r = 0; r < e.length; r += 1) {
40
+ let i = `Лист ${r + 1}`, a = e[r]?.replace(p, " ").replace(h, " ").trim().replace(m, "").trim() || i, o = a.slice(0, f), s = 2;
41
+ for (; t.has(o.toLocaleLowerCase());) {
42
+ let e = ` (${s})`;
43
+ o = `${a.slice(0, f - e.length)}${e}`, s += 1;
44
+ }
45
+ t.add(o.toLocaleLowerCase()), n.push(o);
46
+ }
47
+ return n;
33
48
  }
34
- function g(e, t) {
49
+ function x(e, t) {
35
50
  if (!(e == null || e === "")) {
36
51
  if (t === Number) {
37
52
  let t = typeof e == "number" ? e : Number(e);
@@ -40,7 +55,7 @@ function g(e, t) {
40
55
  return t === String ? String(e) : t === Boolean ? !!e : t === Date ? e instanceof Date ? e : i(e) ?? void 0 : e;
41
56
  }
42
57
  }
43
- function _(e) {
58
+ function S(e) {
44
59
  let t = [e.columns.map((e) => ({
45
60
  type: String,
46
61
  value: e.header,
@@ -49,7 +64,7 @@ function _(e) {
49
64
  for (let n = 0; n < e.rows.length; n++) {
50
65
  let r = e.rows[n], i = [];
51
66
  for (let t = 0; t < e.columns.length; t++) {
52
- let a = e.columns[t], o = g(r[a.id], a.type), s = e.getCellStyle?.({
67
+ let a = e.columns[t], o = x(r[a.id], a.type), s = e.getCellStyle?.({
53
68
  row: r,
54
69
  column: a,
55
70
  rowIndex: n,
@@ -66,66 +81,86 @@ function _(e) {
66
81
  }
67
82
  return t;
68
83
  }
69
- async function v(e) {
70
- let t = _(e), n = h(e.autoFilter ? m(e.columns.length, t.length) : void 0);
71
- await l(t, { columns: e.columns.map((e) => ({ width: e.width })) }, { features: n ? [n] : void 0 }).toFile(e.fileName);
84
+ async function C(e) {
85
+ if (e.sheets.length === 0) throw Error("Для формирования Excel-книги нужен минимум один лист");
86
+ let t = b(e.sheets.map((e) => e.name)), n = [], r = e.sheets.map((e, r) => {
87
+ let i = S(e);
88
+ return n.push(e.autoFilter ? v(e.columns.length, i.length) : void 0), {
89
+ data: i,
90
+ sheet: t[r],
91
+ columns: e.columns.map((e) => ({ width: e.width }))
92
+ };
93
+ }), i = y(n);
94
+ await l(r, { features: i ? [i] : void 0 }).toFile(e.fileName);
95
+ }
96
+ async function w(e) {
97
+ await C({
98
+ fileName: e.fileName,
99
+ sheets: [{
100
+ name: "Sheet1",
101
+ columns: e.columns,
102
+ rows: e.rows,
103
+ autoFilter: e.autoFilter,
104
+ getCellStyle: e.getCellStyle
105
+ }]
106
+ });
72
107
  }
73
108
  //#endregion
74
109
  //#region src/excel/tableExport.ts
75
- var y = "#,##0", b = "#,##0.0", x = "dd.mm.yyyy", S = "d mmm", C = "d mmmm", w = "hh:mm", T = "hh:mm:ss", E = `${x} ${w}`, D = `${x} ${T}`;
76
- function O(e) {
110
+ var T = "#,##0", E = "#,##0.0", D = "dd.mm.yyyy", O = "d mmm", k = "d mmmm", A = "hh:mm", j = "hh:mm:ss", M = `${D} ${A}`, N = `${D} ${j}`;
111
+ function P(e) {
77
112
  let t = s(e).plan?.steps.find((e) => e.type === "typedValueFormat");
78
113
  return t?.type === "typedValueFormat" ? t.config : void 0;
79
114
  }
80
- function k(t) {
115
+ function F(t) {
81
116
  return t.clientOnly ? Number : e(t.odataType) ? Date : n(t.odataType) ? Boolean : r(t.odataType) || t.role === "measure" ? Number : String;
82
117
  }
83
- function A(e) {
84
- let n = O(e.formattersPipeline)?.numberPresetName?.trim();
118
+ function I(e) {
119
+ let n = P(e.formattersPipeline)?.numberPresetName?.trim();
85
120
  return n && c(n) ? n : t(e.odataType) ? "integer" : "decimal";
86
121
  }
87
- function j(e) {
88
- let t = c(A(e));
89
- return t ? t.decimals > 0 ? `#,##0.${"0".repeat(t.decimals)}` : y : b;
122
+ function L(e) {
123
+ let t = c(I(e));
124
+ return t ? t.decimals > 0 ? `#,##0.${"0".repeat(t.decimals)}` : T : E;
90
125
  }
91
- function M(e) {
92
- return O(e.formattersPipeline)?.datePresetName?.trim() || (e.odataType === "time" ? a.time : e.odataType === "datetime" || e.odataType === "datetimeOffset" ? a.datetime : a.date);
126
+ function R(e) {
127
+ return P(e.formattersPipeline)?.datePresetName?.trim() || (e.odataType === "time" ? a.time : e.odataType === "datetime" || e.odataType === "datetimeOffset" ? a.datetime : a.date);
93
128
  }
94
- function N(e) {
129
+ function z(e) {
95
130
  return e.replace(/H/g, "h").replace(/M/g, "m");
96
131
  }
97
- function P(e) {
98
- if (e === a.odataDatetime || e === a.abapDatetime) return D;
99
- if (e === a.odataDate || e === a.abapDate) return x;
100
- if (e === a.monthShort) return S;
101
- if (e === a.monthLong) return C;
102
- if (e.startsWith("datetime")) return e === a.datetime || e === a.datetimeShort ? E : D;
103
- if (e.startsWith("time")) return e === a.time || e === a.timeShort ? w : T;
104
- if (e.startsWith("date")) return x;
132
+ function B(e) {
133
+ if (e === a.odataDatetime || e === a.abapDatetime) return N;
134
+ if (e === a.odataDate || e === a.abapDate) return D;
135
+ if (e === a.monthShort) return O;
136
+ if (e === a.monthLong) return k;
137
+ if (e.startsWith("datetime")) return e === a.datetime || e === a.datetimeShort ? M : N;
138
+ if (e.startsWith("time")) return e === a.time || e === a.timeShort ? A : j;
139
+ if (e.startsWith("date")) return D;
105
140
  }
106
- function F(e, t) {
107
- return P(e) || (t?.pattern ? N(t.pattern) : N(e));
141
+ function V(e, t) {
142
+ return B(e) || (t?.pattern ? z(t.pattern) : z(e));
108
143
  }
109
- function I(e) {
110
- let t = M(e);
111
- return F(t, o(t));
144
+ function H(e) {
145
+ let t = R(e);
146
+ return V(t, o(t));
112
147
  }
113
- function L(e, t) {
114
- if (t === Number) return j(e);
115
- if (t === Date) return I(e);
148
+ function U(e, t) {
149
+ if (t === Number) return L(e);
150
+ if (t === Date) return H(e);
116
151
  }
117
- function R(e) {
118
- let t = k(e);
152
+ function W(e) {
153
+ let t = F(e);
119
154
  return {
120
155
  id: e.id,
121
156
  header: e.header,
122
157
  width: e.width,
123
158
  type: t,
124
- format: L(e, t)
159
+ format: U(e, t)
125
160
  };
126
161
  }
127
- async function z({ fileName: e, table: t, autoFilter: n = !0 }) {
128
- await v({
162
+ async function G({ fileName: e, table: t, autoFilter: n = !0 }) {
163
+ await w({
129
164
  fileName: e,
130
165
  columns: t.columns,
131
166
  rows: t.rows,
@@ -133,7 +168,19 @@ async function z({ fileName: e, table: t, autoFilter: n = !0 }) {
133
168
  getCellStyle: ({ rowIndex: e, column: n }) => t.cellStyles?.[e]?.[n.id]
134
169
  });
135
170
  }
171
+ async function K({ fileName: e, sheets: t }) {
172
+ await C({
173
+ fileName: e,
174
+ sheets: t.map(({ name: e, table: t, autoFilter: n = !0 }) => ({
175
+ name: e,
176
+ columns: t.columns,
177
+ rows: t.rows,
178
+ autoFilter: n,
179
+ getCellStyle: ({ rowIndex: e, column: n }) => t.cellStyles?.[e]?.[n.id]
180
+ }))
181
+ });
182
+ }
136
183
  //#endregion
137
- export { m as buildExcelAutoFilterRef, _ as buildExcelSheetData, R as createTableExcelColumn, v as downloadExcelFile, z as downloadResolvedExcelTable, f as resolveExcelCellStyleFromState, k as resolveTableExcelColumnType };
184
+ export { v as buildExcelAutoFilterRef, S as buildExcelSheetData, W as createTableExcelColumn, w as downloadExcelFile, C as downloadExcelWorkbook, G as downloadResolvedExcelTable, K as downloadResolvedExcelWorkbook, g as resolveExcelCellStyleFromState, b as resolveExcelSheetNames, F as resolveTableExcelColumnType };
138
185
 
139
186
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/excel/excel.ts","../../src/excel/tableExport.ts"],"sourcesContent":["import writeXlsxFile, { type Cell, type CellObject, type Feature, type SheetData } from \"write-excel-file/browser\";\n\nimport { parseDate } from \"../formatters/date\";\nimport { type State } from \"../types\";\n\nexport type ExcelColumn = {\n\tid: string;\n\theader: string;\n\twidth?: number;\n\ttype?: StringConstructor | NumberConstructor | DateConstructor | BooleanConstructor;\n\tformat?: string;\n};\n\nexport type ExcelRow = Record<string, unknown>;\n\nexport type ExcelCellStyle = Omit<CellObject, \"value\" | \"type\" | \"format\">;\n\nexport type DownloadExcelFileArgs = {\n\tfileName: string;\n\tcolumns: ExcelColumn[];\n\trows: ExcelRow[];\n\tautoFilter?: boolean;\n\tgetCellStyle?: (args: { row: ExcelRow; column: ExcelColumn; rowIndex: number; columnIndex: number }) => ExcelCellStyle | undefined;\n};\n\ntype BrowserExcelFileContent = File | Blob | ArrayBuffer;\n\nconst VALUE_STATE_TEXT_COLORS: Partial<Record<State, string>> = {\n\tinformation: \"#2563EB\",\n\tsuccess: \"#107C10\",\n\twarning: \"#B7791F\",\n\terror: \"#C50F1F\"\n};\n\nconst HEADER_CELL_STYLE = {\n\tfontWeight: \"bold\",\n\tbottomBorderStyle: \"thin\",\n\tbottomBorderColor: \"#D9D9D9\"\n} satisfies ExcelCellStyle;\n\n/**\n * Приводит общий value-state проекта к базовому Excel-стилю.\n *\n * Helper живет в shared Excel-слое, чтобы analytical/detail экспорт не\n * дублировали палитру статусов и одинаково подсвечивали warning/error/success.\n */\nexport function resolveExcelCellStyleFromState(state: State | undefined): ExcelCellStyle | undefined {\n\tconst textColor = state ? VALUE_STATE_TEXT_COLORS[state] : undefined;\n\treturn textColor ? { textColor } : undefined;\n}\n\n/**\n * Переводит zero-based индекс колонки в Excel-адрес (`0 -> A`, `27 -> AB`).\n * Нужен для XML-диапазонов, потому что `write-excel-file` не предоставляет\n * отдельного API для построения ссылок вида `A1:C10`.\n */\nfunction numberToColumnName(index: number): string {\n\tlet value = index + 1;\n\tlet out = \"\";\n\n\twhile (value > 0) {\n\t\tconst remainder = (value - 1) % 26;\n\t\tout = String.fromCharCode(65 + remainder) + out;\n\t\tvalue = Math.floor((value - 1) / 26);\n\t}\n\n\treturn out;\n}\n\n/**\n * Строит диапазон автофильтра для всей выгруженной таблицы.\n *\n * Возвращает `undefined` для пустой сетки, чтобы не вставлять в xlsx\n * невалидный `<autoFilter>` без реального диапазона.\n */\nexport function buildExcelAutoFilterRef(columnCount: number, rowCount: number): string | undefined {\n\tif (columnCount <= 0 || rowCount <= 0) return undefined;\n\n\treturn `A1:${numberToColumnName(columnCount - 1)}${rowCount}`;\n}\n\n/**\n * Добавляет autoFilter через feature-transform `write-excel-file`.\n *\n * Библиотека умеет трансформировать XML листа, но не имеет высокоуровневого\n * параметра `autoFilter`, поэтому здесь изолирован низкоуровневый OpenXML-хук.\n */\nfunction createAutoFilterFeature<FileContent>(ref: string | undefined): Feature<FileContent> | undefined {\n\tif (!ref) return undefined;\n\n\treturn {\n\t\tfiles: {\n\t\t\ttransform: {\n\t\t\t\t\"xl/worksheets/sheet{id}.xml\": {\n\t\t\t\t\ttransform: (content) => content.replace(\"</sheetData>\", `</sheetData><autoFilter ref=\"${ref}\"/>`)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n}\n\n/**\n * Приводит входное значение к типу Excel-ячейки.\n *\n * Важный смысл: если колонка числовая, в файл должен попасть настоящий Number,\n * а не уже отформатированная строка. Тогда Excel сможет применять `format`.\n */\nfunction normalizeCellValue(value: unknown, type: ExcelColumn[\"type\"]) {\n\tif (value == null || value === \"\") return undefined;\n\n\tif (type === Number) {\n\t\tconst numberValue = typeof value === \"number\" ? value : Number(value);\n\t\treturn Number.isFinite(numberValue) ? numberValue : undefined;\n\t}\n\n\tif (type === String) return String(value);\n\tif (type === Boolean) return Boolean(value);\n\tif (type === Date) return value instanceof Date ? value : (parseDate(value) ?? undefined);\n\n\treturn value as CellObject[\"value\"];\n}\n\n/**\n * Собирает SheetData для `write-excel-file`.\n *\n * Функция намеренно не знает о бизнес-сущностях: ей передают уже готовые\n * колонки/строки и необязательный callback стилей конкретных ячеек.\n */\nexport function buildExcelSheetData(args: Omit<DownloadExcelFileArgs, \"fileName\" | \"autoFilter\">): SheetData {\n\tconst headerRow: Cell[] = args.columns.map((column) => ({\n\t\ttype: String,\n\t\tvalue: column.header,\n\t\t...HEADER_CELL_STYLE\n\t}));\n\n\tconst sheetRows: SheetData = [headerRow];\n\n\tfor (let rowIndex = 0; rowIndex < args.rows.length; rowIndex++) {\n\t\tconst row = args.rows[rowIndex];\n\t\tconst sheetRow: Cell[] = [];\n\n\t\tfor (let columnIndex = 0; columnIndex < args.columns.length; columnIndex++) {\n\t\t\tconst column = args.columns[columnIndex];\n\t\t\tconst value = normalizeCellValue(row[column.id], column.type);\n\t\t\tconst style = args.getCellStyle?.({ row, column, rowIndex, columnIndex });\n\n\t\t\tsheetRow.push({\n\t\t\t\tvalue,\n\t\t\t\ttype: column.type,\n\t\t\t\tformat: column.format,\n\t\t\t\t...style\n\t\t\t});\n\t\t}\n\n\t\tsheetRows.push(sheetRow);\n\t}\n\n\treturn sheetRows;\n}\n\n/**\n * Формирует и скачивает `.xlsx` в браузере.\n *\n * Это публичная generic-точка shared-слоя: она отвечает только за Excel-файл,\n * а подготовка доменных данных остаётся на вызывающей стороне.\n */\nexport async function downloadExcelFile(args: DownloadExcelFileArgs): Promise<void> {\n\tconst sheetData = buildExcelSheetData(args);\n\tconst autoFilterRef = args.autoFilter ? buildExcelAutoFilterRef(args.columns.length, sheetData.length) : undefined;\n\tconst autoFilterFeature = createAutoFilterFeature<BrowserExcelFileContent>(autoFilterRef);\n\n\tawait writeXlsxFile(\n\t\tsheetData,\n\t\t{\n\t\t\tcolumns: args.columns.map((column) => ({\n\t\t\t\twidth: column.width\n\t\t\t}))\n\t\t},\n\t\t{\n\t\t\tfeatures: autoFilterFeature ? [autoFilterFeature] : undefined\n\t\t}\n\t).toFile(args.fileName);\n}\n","import { DEFAULT_DATE_PRESET_NAMES, getDatePreset, type DateFormatPreset } from \"../formatters/date\";\nimport { getNumberPreset } from \"../formatters/number\";\nimport {\n\tvalidateFormattersPipelineConfig,\n\ttype FormattersPipelineConfig,\n\ttype FormattersPipelineTypedValueFormatConfig\n} from \"../formatters/pipeline\";\nimport {\n\tisODataBooleanType,\n\tisODataDateType,\n\tisODataIntegerType,\n\tisODataNumericType,\n\ttype ColumnRole,\n\ttype ODataMetaType\n} from \"../odata-service\";\n\nimport { downloadExcelFile, type ExcelCellStyle, type ExcelColumn, type ExcelRow } from \"./excel\";\n\nexport type TableExcelColumnDescriptor = {\n\tid: string;\n\theader: string;\n\twidth?: number;\n\trole?: ColumnRole;\n\todataType?: ODataMetaType;\n\tclientOnly?: boolean;\n\tformattersPipeline?: FormattersPipelineConfig;\n};\n\nexport type ResolvedExcelTable = {\n\tcolumns: ExcelColumn[];\n\trows: ExcelRow[];\n\tcellStyles?: ReadonlyArray<Readonly<Record<string, ExcelCellStyle | undefined>>>;\n};\n\nexport type DownloadResolvedExcelTableArgs = {\n\tfileName: string;\n\ttable: ResolvedExcelTable;\n\tautoFilter?: boolean;\n};\n\nconst INTEGER_EXCEL_FORMAT = \"#,##0\";\nconst DEFAULT_DECIMAL_EXCEL_FORMAT = \"#,##0.0\";\nconst EXCEL_DATE_FORMAT = \"dd.mm.yyyy\";\nconst EXCEL_DAY_MONTH_SHORT_FORMAT = \"d mmm\";\nconst EXCEL_DAY_MONTH_LONG_FORMAT = \"d mmmm\";\nconst EXCEL_TIME_FORMAT = \"hh:mm\";\nconst EXCEL_TIME_SECONDS_FORMAT = \"hh:mm:ss\";\nconst EXCEL_DATETIME_FORMAT = `${EXCEL_DATE_FORMAT} ${EXCEL_TIME_FORMAT}`;\nconst EXCEL_DATETIME_SECONDS_FORMAT = `${EXCEL_DATE_FORMAT} ${EXCEL_TIME_SECONDS_FORMAT}`;\n\nfunction resolveTypedValueFormatConfig(\n\tformattersPipeline: FormattersPipelineConfig | undefined\n): FormattersPipelineTypedValueFormatConfig | undefined {\n\tconst validation = validateFormattersPipelineConfig(formattersPipeline);\n\tconst typedValueFormatStep = validation.plan?.steps.find((step) => step.type === \"typedValueFormat\");\n\n\treturn typedValueFormatStep?.type === \"typedValueFormat\" ? typedValueFormatStep.config : undefined;\n}\n\nexport function resolveTableExcelColumnType(descriptor: TableExcelColumnDescriptor): ExcelColumn[\"type\"] {\n\tif (descriptor.clientOnly) return Number;\n\tif (isODataDateType(descriptor.odataType)) return Date;\n\tif (isODataBooleanType(descriptor.odataType)) return Boolean;\n\tif (isODataNumericType(descriptor.odataType)) return Number;\n\tif (descriptor.role === \"measure\") return Number;\n\treturn String;\n}\n\nfunction resolveNumberPresetName(descriptor: TableExcelColumnDescriptor): string {\n\tconst configuredPresetName = resolveTypedValueFormatConfig(descriptor.formattersPipeline)?.numberPresetName?.trim();\n\tif (configuredPresetName && getNumberPreset(configuredPresetName)) return configuredPresetName;\n\tif (isODataIntegerType(descriptor.odataType)) return \"integer\";\n\treturn \"decimal\";\n}\n\nfunction resolveExcelNumberFormat(descriptor: TableExcelColumnDescriptor): string {\n\tconst preset = getNumberPreset(resolveNumberPresetName(descriptor));\n\tif (!preset) return DEFAULT_DECIMAL_EXCEL_FORMAT;\n\treturn preset.decimals > 0 ? `#,##0.${\"0\".repeat(preset.decimals)}` : INTEGER_EXCEL_FORMAT;\n}\n\nfunction resolveDatePresetName(descriptor: TableExcelColumnDescriptor): string {\n\tconst configuredPresetName = resolveTypedValueFormatConfig(descriptor.formattersPipeline)?.datePresetName?.trim();\n\tif (configuredPresetName) return configuredPresetName;\n\tif (descriptor.odataType === \"time\") return DEFAULT_DATE_PRESET_NAMES.time;\n\tif (descriptor.odataType === \"datetime\" || descriptor.odataType === \"datetimeOffset\") return DEFAULT_DATE_PRESET_NAMES.datetime;\n\treturn DEFAULT_DATE_PRESET_NAMES.date;\n}\n\nfunction normalizeDatePatternForExcel(pattern: string): string {\n\treturn pattern.replace(/H/g, \"h\").replace(/M/g, \"m\");\n}\n\nfunction resolveBuiltinDatePresetExcelFormat(presetName: string): string | undefined {\n\tif (presetName === DEFAULT_DATE_PRESET_NAMES.odataDatetime || presetName === DEFAULT_DATE_PRESET_NAMES.abapDatetime) {\n\t\treturn EXCEL_DATETIME_SECONDS_FORMAT;\n\t}\n\n\tif (presetName === DEFAULT_DATE_PRESET_NAMES.odataDate || presetName === DEFAULT_DATE_PRESET_NAMES.abapDate) {\n\t\treturn EXCEL_DATE_FORMAT;\n\t}\n\n\tif (presetName === DEFAULT_DATE_PRESET_NAMES.monthShort) {\n\t\treturn EXCEL_DAY_MONTH_SHORT_FORMAT;\n\t}\n\n\tif (presetName === DEFAULT_DATE_PRESET_NAMES.monthLong) {\n\t\treturn EXCEL_DAY_MONTH_LONG_FORMAT;\n\t}\n\n\tif (presetName.startsWith(\"datetime\")) {\n\t\treturn presetName === DEFAULT_DATE_PRESET_NAMES.datetime || presetName === DEFAULT_DATE_PRESET_NAMES.datetimeShort\n\t\t\t? EXCEL_DATETIME_FORMAT\n\t\t\t: EXCEL_DATETIME_SECONDS_FORMAT;\n\t}\n\n\tif (presetName.startsWith(\"time\")) {\n\t\treturn presetName === DEFAULT_DATE_PRESET_NAMES.time || presetName === DEFAULT_DATE_PRESET_NAMES.timeShort\n\t\t\t? EXCEL_TIME_FORMAT\n\t\t\t: EXCEL_TIME_SECONDS_FORMAT;\n\t}\n\n\tif (presetName.startsWith(\"date\")) return EXCEL_DATE_FORMAT;\n\n\treturn undefined;\n}\n\nfunction resolveExcelDateFormatFromPreset(presetName: string, preset: DateFormatPreset | undefined): string {\n\tconst builtinFormat = resolveBuiltinDatePresetExcelFormat(presetName);\n\tif (builtinFormat) return builtinFormat;\n\tif (preset?.pattern) return normalizeDatePatternForExcel(preset.pattern);\n\n\t// Настройка datePresetName исторически хранит и ручной шаблон. Если имени\n\t// нет в реестре, передаём шаблон в Excel как есть после нормализации токенов.\n\treturn normalizeDatePatternForExcel(presetName);\n}\n\nfunction resolveExcelDateFormat(descriptor: TableExcelColumnDescriptor): string {\n\tconst presetName = resolveDatePresetName(descriptor);\n\treturn resolveExcelDateFormatFromPreset(presetName, getDatePreset(presetName));\n}\n\nfunction resolveTableExcelColumnFormat(descriptor: TableExcelColumnDescriptor, type: ExcelColumn[\"type\"]): string | undefined {\n\tif (type === Number) return resolveExcelNumberFormat(descriptor);\n\tif (type === Date) return resolveExcelDateFormat(descriptor);\n\treturn undefined;\n}\n\nexport function createTableExcelColumn(descriptor: TableExcelColumnDescriptor): ExcelColumn {\n\tconst type = resolveTableExcelColumnType(descriptor);\n\treturn {\n\t\tid: descriptor.id,\n\t\theader: descriptor.header,\n\t\twidth: descriptor.width,\n\t\ttype,\n\t\tformat: resolveTableExcelColumnFormat(descriptor, type)\n\t};\n}\n\nexport async function downloadResolvedExcelTable({ fileName, table, autoFilter = true }: DownloadResolvedExcelTableArgs): Promise<void> {\n\tawait downloadExcelFile({\n\t\tfileName,\n\t\tcolumns: table.columns,\n\t\trows: table.rows,\n\t\tautoFilter,\n\t\tgetCellStyle: ({ rowIndex, column }) => table.cellStyles?.[rowIndex]?.[column.id]\n\t});\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/excel/excel.ts","../../src/excel/tableExport.ts"],"sourcesContent":["import writeXlsxFile, { type Cell, type CellObject, type Feature, type Sheet, type SheetData } from \"write-excel-file/browser\";\n\nimport { parseDate } from \"../formatters/date\";\nimport { type State } from \"../types\";\n\nexport type ExcelColumn = {\n\tid: string;\n\theader: string;\n\twidth?: number;\n\ttype?: StringConstructor | NumberConstructor | DateConstructor | BooleanConstructor;\n\tformat?: string;\n};\n\nexport type ExcelRow = Record<string, unknown>;\n\nexport type ExcelCellStyle = Omit<CellObject, \"value\" | \"type\" | \"format\">;\n\nexport type DownloadExcelFileArgs = {\n\tfileName: string;\n\tcolumns: ExcelColumn[];\n\trows: ExcelRow[];\n\tautoFilter?: boolean;\n\tgetCellStyle?: (args: { row: ExcelRow; column: ExcelColumn; rowIndex: number; columnIndex: number }) => ExcelCellStyle | undefined;\n};\n\n/** Описание одного табличного листа внутри Excel-книги. */\nexport type ExcelWorkbookSheet = Omit<DownloadExcelFileArgs, \"fileName\"> & {\n\t/** Желаемое имя листа; writer нормализует ограничения формата и конфликты имён. */\n\tname: string;\n};\n\n/** Аргументы формирования Excel-книги с одним или несколькими табличными листами. */\nexport type DownloadExcelWorkbookArgs = {\n\t/** Имя скачиваемого `.xlsx` файла. */\n\tfileName: string;\n\t/** Листы книги в порядке отображения в Excel. */\n\tsheets: ExcelWorkbookSheet[];\n};\n\ntype BrowserExcelFileContent = File | Blob | ArrayBuffer;\n\nconst VALUE_STATE_TEXT_COLORS: Partial<Record<State, string>> = {\n\tinformation: \"#2563EB\",\n\tsuccess: \"#107C10\",\n\twarning: \"#B7791F\",\n\terror: \"#C50F1F\"\n};\n\nconst HEADER_CELL_STYLE = {\n\tfontWeight: \"bold\",\n\tbottomBorderStyle: \"thin\",\n\tbottomBorderColor: \"#D9D9D9\"\n} satisfies ExcelCellStyle;\n\nconst EXCEL_SHEET_NAME_MAX_LENGTH = 31;\nconst EXCEL_SHEET_NAME_INVALID_CHARACTERS = /[\\u0000-\\u001f[\\]/\\\\:*?]+/g;\nconst EXCEL_SHEET_NAME_EDGE_APOSTROPHES = /^'+|'+$/g;\nconst EXCEL_SHEET_NAME_WHITESPACE = /\\s+/g;\n\n/**\n * Приводит общий value-state проекта к базовому Excel-стилю.\n *\n * Helper живет в shared Excel-слое, чтобы analytical/detail экспорт не\n * дублировали палитру статусов и одинаково подсвечивали warning/error/success.\n */\nexport function resolveExcelCellStyleFromState(state: State | undefined): ExcelCellStyle | undefined {\n\tconst textColor = state ? VALUE_STATE_TEXT_COLORS[state] : undefined;\n\treturn textColor ? { textColor } : undefined;\n}\n\n/**\n * Переводит zero-based индекс колонки в Excel-адрес (`0 -> A`, `27 -> AB`).\n * Нужен для XML-диапазонов, потому что `write-excel-file` не предоставляет\n * отдельного API для построения ссылок вида `A1:C10`.\n */\nfunction numberToColumnName(index: number): string {\n\tlet value = index + 1;\n\tlet out = \"\";\n\n\twhile (value > 0) {\n\t\tconst remainder = (value - 1) % 26;\n\t\tout = String.fromCharCode(65 + remainder) + out;\n\t\tvalue = Math.floor((value - 1) / 26);\n\t}\n\n\treturn out;\n}\n\n/**\n * Строит диапазон автофильтра для всей выгруженной таблицы.\n *\n * Возвращает `undefined` для пустой сетки, чтобы не вставлять в xlsx\n * невалидный `<autoFilter>` без реального диапазона.\n */\nexport function buildExcelAutoFilterRef(columnCount: number, rowCount: number): string | undefined {\n\tif (columnCount <= 0 || rowCount <= 0) return undefined;\n\n\treturn `A1:${numberToColumnName(columnCount - 1)}${rowCount}`;\n}\n\n/**\n * Добавляет autoFilter через feature-transform `write-excel-file`.\n *\n * Библиотека умеет трансформировать XML листа, но не имеет высокоуровневого\n * параметра `autoFilter`, поэтому здесь изолирован низкоуровневый OpenXML-хук.\n */\nfunction createAutoFilterFeature<FileContent>(refs: readonly (string | undefined)[]): Feature<FileContent> | undefined {\n\tif (!refs.some(Boolean)) return undefined;\n\n\treturn {\n\t\tfiles: {\n\t\t\ttransform: {\n\t\t\t\t\"xl/worksheets/sheet{id}.xml\": {\n\t\t\t\t\ttransform: (content, _options, properties) => {\n\t\t\t\t\t\tconst ref = refs[properties.sheetIndex];\n\t\t\t\t\t\treturn ref ? content.replace(\"</sheetData>\", `</sheetData><autoFilter ref=\"${ref}\"/>`) : content;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n}\n\n/**\n * Нормализует имена листов по ограничениям Excel и делает их уникальными.\n *\n * Runtime-заголовки могут содержать запрещённые символы, превышать 31 знак или\n * совпадать между несколькими представлениями одной конфигурации. Нормализация\n * выполняется централизованно, чтобы все consumers получали открываемую книгу.\n */\nexport function resolveExcelSheetNames(names: readonly string[]): string[] {\n\tconst usedNames = new Set<string>();\n\tconst resolvedNames: string[] = [];\n\n\tfor (let index = 0; index < names.length; index += 1) {\n\t\tconst fallbackName = `Лист ${index + 1}`;\n\t\tconst normalizedName = names[index]\n\t\t\t?.replace(EXCEL_SHEET_NAME_INVALID_CHARACTERS, \" \")\n\t\t\t.replace(EXCEL_SHEET_NAME_WHITESPACE, \" \")\n\t\t\t.trim()\n\t\t\t.replace(EXCEL_SHEET_NAME_EDGE_APOSTROPHES, \"\")\n\t\t\t.trim();\n\t\tconst baseName = normalizedName || fallbackName;\n\t\tlet resolvedName = baseName.slice(0, EXCEL_SHEET_NAME_MAX_LENGTH);\n\t\tlet duplicateIndex = 2;\n\n\t\twhile (usedNames.has(resolvedName.toLocaleLowerCase())) {\n\t\t\tconst suffix = ` (${duplicateIndex})`;\n\t\t\tresolvedName = `${baseName.slice(0, EXCEL_SHEET_NAME_MAX_LENGTH - suffix.length)}${suffix}`;\n\t\t\tduplicateIndex += 1;\n\t\t}\n\n\t\tusedNames.add(resolvedName.toLocaleLowerCase());\n\t\tresolvedNames.push(resolvedName);\n\t}\n\n\treturn resolvedNames;\n}\n\n/**\n * Приводит входное значение к типу Excel-ячейки.\n *\n * Важный смысл: если колонка числовая, в файл должен попасть настоящий Number,\n * а не уже отформатированная строка. Тогда Excel сможет применять `format`.\n */\nfunction normalizeCellValue(value: unknown, type: ExcelColumn[\"type\"]) {\n\tif (value == null || value === \"\") return undefined;\n\n\tif (type === Number) {\n\t\tconst numberValue = typeof value === \"number\" ? value : Number(value);\n\t\treturn Number.isFinite(numberValue) ? numberValue : undefined;\n\t}\n\n\tif (type === String) return String(value);\n\tif (type === Boolean) return Boolean(value);\n\tif (type === Date) return value instanceof Date ? value : (parseDate(value) ?? undefined);\n\n\treturn value as CellObject[\"value\"];\n}\n\n/**\n * Собирает SheetData для `write-excel-file`.\n *\n * Функция намеренно не знает о бизнес-сущностях: ей передают уже готовые\n * колонки/строки и необязательный callback стилей конкретных ячеек.\n */\nexport function buildExcelSheetData(args: Omit<DownloadExcelFileArgs, \"fileName\" | \"autoFilter\">): SheetData {\n\tconst headerRow: Cell[] = args.columns.map((column) => ({\n\t\ttype: String,\n\t\tvalue: column.header,\n\t\t...HEADER_CELL_STYLE\n\t}));\n\n\tconst sheetRows: SheetData = [headerRow];\n\n\tfor (let rowIndex = 0; rowIndex < args.rows.length; rowIndex++) {\n\t\tconst row = args.rows[rowIndex];\n\t\tconst sheetRow: Cell[] = [];\n\n\t\tfor (let columnIndex = 0; columnIndex < args.columns.length; columnIndex++) {\n\t\t\tconst column = args.columns[columnIndex];\n\t\t\tconst value = normalizeCellValue(row[column.id], column.type);\n\t\t\tconst style = args.getCellStyle?.({ row, column, rowIndex, columnIndex });\n\n\t\t\tsheetRow.push({\n\t\t\t\tvalue,\n\t\t\t\ttype: column.type,\n\t\t\t\tformat: column.format,\n\t\t\t\t...style\n\t\t\t});\n\t\t}\n\n\t\tsheetRows.push(sheetRow);\n\t}\n\n\treturn sheetRows;\n}\n\n/**\n * Формирует и скачивает Excel-книгу с независимыми табличными листами.\n *\n * Каждый лист получает собственные ширины колонок, стили ячеек и диапазон\n * автофильтра. Доменная подготовка колонок и строк остаётся у вызывающей стороны.\n */\nexport async function downloadExcelWorkbook(args: DownloadExcelWorkbookArgs): Promise<void> {\n\tif (args.sheets.length === 0) {\n\t\tthrow new Error(\"Для формирования Excel-книги нужен минимум один лист\");\n\t}\n\n\tconst sheetNames = resolveExcelSheetNames(args.sheets.map((sheet) => sheet.name));\n\tconst autoFilterRefs: Array<string | undefined> = [];\n\tconst sheets: Sheet<BrowserExcelFileContent>[] = args.sheets.map((sheet, index) => {\n\t\tconst data = buildExcelSheetData(sheet);\n\t\tautoFilterRefs.push(sheet.autoFilter ? buildExcelAutoFilterRef(sheet.columns.length, data.length) : undefined);\n\n\t\treturn {\n\t\t\tdata,\n\t\t\tsheet: sheetNames[index],\n\t\t\tcolumns: sheet.columns.map((column) => ({\n\t\t\t\twidth: column.width\n\t\t\t}))\n\t\t};\n\t});\n\tconst autoFilterFeature = createAutoFilterFeature<BrowserExcelFileContent>(autoFilterRefs);\n\n\tawait writeXlsxFile(sheets, {\n\t\tfeatures: autoFilterFeature ? [autoFilterFeature] : undefined\n\t}).toFile(args.fileName);\n}\n\n/**\n * Формирует и скачивает `.xlsx` в браузере.\n *\n * Это публичная generic-точка shared-слоя: она отвечает только за Excel-файл,\n * а подготовка доменных данных остаётся на вызывающей стороне.\n */\nexport async function downloadExcelFile(args: DownloadExcelFileArgs): Promise<void> {\n\tawait downloadExcelWorkbook({\n\t\tfileName: args.fileName,\n\t\tsheets: [\n\t\t\t{\n\t\t\t\tname: \"Sheet1\",\n\t\t\t\tcolumns: args.columns,\n\t\t\t\trows: args.rows,\n\t\t\t\tautoFilter: args.autoFilter,\n\t\t\t\tgetCellStyle: args.getCellStyle\n\t\t\t}\n\t\t]\n\t});\n}\n","import { DEFAULT_DATE_PRESET_NAMES, getDatePreset, type DateFormatPreset } from \"../formatters/date\";\nimport { getNumberPreset } from \"../formatters/number\";\nimport {\n\tvalidateFormattersPipelineConfig,\n\ttype FormattersPipelineConfig,\n\ttype FormattersPipelineTypedValueFormatConfig\n} from \"../formatters/pipeline\";\nimport {\n\tisODataBooleanType,\n\tisODataDateType,\n\tisODataIntegerType,\n\tisODataNumericType,\n\ttype ColumnRole,\n\ttype ODataMetaType\n} from \"../odata-service\";\n\nimport { downloadExcelFile, downloadExcelWorkbook, type ExcelCellStyle, type ExcelColumn, type ExcelRow } from \"./excel\";\n\nexport type TableExcelColumnDescriptor = {\n\tid: string;\n\theader: string;\n\twidth?: number;\n\trole?: ColumnRole;\n\todataType?: ODataMetaType;\n\tclientOnly?: boolean;\n\tformattersPipeline?: FormattersPipelineConfig;\n};\n\nexport type ResolvedExcelTable = {\n\tcolumns: ExcelColumn[];\n\trows: ExcelRow[];\n\tcellStyles?: ReadonlyArray<Readonly<Record<string, ExcelCellStyle | undefined>>>;\n};\n\nexport type DownloadResolvedExcelTableArgs = {\n\tfileName: string;\n\ttable: ResolvedExcelTable;\n\tautoFilter?: boolean;\n};\n\n/** Один подготовленный табличный лист generic Excel-книги. */\nexport type ResolvedExcelWorkbookSheet = {\n\t/** Желаемое имя листа. */\n\tname: string;\n\t/** Подготовленные колонки, строки и optional стили ячеек. */\n\ttable: ResolvedExcelTable;\n\t/** Включить автофильтр для этого листа. По умолчанию включён. */\n\tautoFilter?: boolean;\n};\n\n/** Аргументы скачивания книги из нескольких подготовленных таблиц. */\nexport type DownloadResolvedExcelWorkbookArgs = {\n\t/** Имя скачиваемого `.xlsx` файла. */\n\tfileName: string;\n\t/** Табличные листы в порядке отображения в книге. */\n\tsheets: ResolvedExcelWorkbookSheet[];\n};\n\nconst INTEGER_EXCEL_FORMAT = \"#,##0\";\nconst DEFAULT_DECIMAL_EXCEL_FORMAT = \"#,##0.0\";\nconst EXCEL_DATE_FORMAT = \"dd.mm.yyyy\";\nconst EXCEL_DAY_MONTH_SHORT_FORMAT = \"d mmm\";\nconst EXCEL_DAY_MONTH_LONG_FORMAT = \"d mmmm\";\nconst EXCEL_TIME_FORMAT = \"hh:mm\";\nconst EXCEL_TIME_SECONDS_FORMAT = \"hh:mm:ss\";\nconst EXCEL_DATETIME_FORMAT = `${EXCEL_DATE_FORMAT} ${EXCEL_TIME_FORMAT}`;\nconst EXCEL_DATETIME_SECONDS_FORMAT = `${EXCEL_DATE_FORMAT} ${EXCEL_TIME_SECONDS_FORMAT}`;\n\nfunction resolveTypedValueFormatConfig(\n\tformattersPipeline: FormattersPipelineConfig | undefined\n): FormattersPipelineTypedValueFormatConfig | undefined {\n\tconst validation = validateFormattersPipelineConfig(formattersPipeline);\n\tconst typedValueFormatStep = validation.plan?.steps.find((step) => step.type === \"typedValueFormat\");\n\n\treturn typedValueFormatStep?.type === \"typedValueFormat\" ? typedValueFormatStep.config : undefined;\n}\n\nexport function resolveTableExcelColumnType(descriptor: TableExcelColumnDescriptor): ExcelColumn[\"type\"] {\n\tif (descriptor.clientOnly) return Number;\n\tif (isODataDateType(descriptor.odataType)) return Date;\n\tif (isODataBooleanType(descriptor.odataType)) return Boolean;\n\tif (isODataNumericType(descriptor.odataType)) return Number;\n\tif (descriptor.role === \"measure\") return Number;\n\treturn String;\n}\n\nfunction resolveNumberPresetName(descriptor: TableExcelColumnDescriptor): string {\n\tconst configuredPresetName = resolveTypedValueFormatConfig(descriptor.formattersPipeline)?.numberPresetName?.trim();\n\tif (configuredPresetName && getNumberPreset(configuredPresetName)) return configuredPresetName;\n\tif (isODataIntegerType(descriptor.odataType)) return \"integer\";\n\treturn \"decimal\";\n}\n\nfunction resolveExcelNumberFormat(descriptor: TableExcelColumnDescriptor): string {\n\tconst preset = getNumberPreset(resolveNumberPresetName(descriptor));\n\tif (!preset) return DEFAULT_DECIMAL_EXCEL_FORMAT;\n\treturn preset.decimals > 0 ? `#,##0.${\"0\".repeat(preset.decimals)}` : INTEGER_EXCEL_FORMAT;\n}\n\nfunction resolveDatePresetName(descriptor: TableExcelColumnDescriptor): string {\n\tconst configuredPresetName = resolveTypedValueFormatConfig(descriptor.formattersPipeline)?.datePresetName?.trim();\n\tif (configuredPresetName) return configuredPresetName;\n\tif (descriptor.odataType === \"time\") return DEFAULT_DATE_PRESET_NAMES.time;\n\tif (descriptor.odataType === \"datetime\" || descriptor.odataType === \"datetimeOffset\") return DEFAULT_DATE_PRESET_NAMES.datetime;\n\treturn DEFAULT_DATE_PRESET_NAMES.date;\n}\n\nfunction normalizeDatePatternForExcel(pattern: string): string {\n\treturn pattern.replace(/H/g, \"h\").replace(/M/g, \"m\");\n}\n\nfunction resolveBuiltinDatePresetExcelFormat(presetName: string): string | undefined {\n\tif (presetName === DEFAULT_DATE_PRESET_NAMES.odataDatetime || presetName === DEFAULT_DATE_PRESET_NAMES.abapDatetime) {\n\t\treturn EXCEL_DATETIME_SECONDS_FORMAT;\n\t}\n\n\tif (presetName === DEFAULT_DATE_PRESET_NAMES.odataDate || presetName === DEFAULT_DATE_PRESET_NAMES.abapDate) {\n\t\treturn EXCEL_DATE_FORMAT;\n\t}\n\n\tif (presetName === DEFAULT_DATE_PRESET_NAMES.monthShort) {\n\t\treturn EXCEL_DAY_MONTH_SHORT_FORMAT;\n\t}\n\n\tif (presetName === DEFAULT_DATE_PRESET_NAMES.monthLong) {\n\t\treturn EXCEL_DAY_MONTH_LONG_FORMAT;\n\t}\n\n\tif (presetName.startsWith(\"datetime\")) {\n\t\treturn presetName === DEFAULT_DATE_PRESET_NAMES.datetime || presetName === DEFAULT_DATE_PRESET_NAMES.datetimeShort\n\t\t\t? EXCEL_DATETIME_FORMAT\n\t\t\t: EXCEL_DATETIME_SECONDS_FORMAT;\n\t}\n\n\tif (presetName.startsWith(\"time\")) {\n\t\treturn presetName === DEFAULT_DATE_PRESET_NAMES.time || presetName === DEFAULT_DATE_PRESET_NAMES.timeShort\n\t\t\t? EXCEL_TIME_FORMAT\n\t\t\t: EXCEL_TIME_SECONDS_FORMAT;\n\t}\n\n\tif (presetName.startsWith(\"date\")) return EXCEL_DATE_FORMAT;\n\n\treturn undefined;\n}\n\nfunction resolveExcelDateFormatFromPreset(presetName: string, preset: DateFormatPreset | undefined): string {\n\tconst builtinFormat = resolveBuiltinDatePresetExcelFormat(presetName);\n\tif (builtinFormat) return builtinFormat;\n\tif (preset?.pattern) return normalizeDatePatternForExcel(preset.pattern);\n\n\t// Настройка datePresetName исторически хранит и ручной шаблон. Если имени\n\t// нет в реестре, передаём шаблон в Excel как есть после нормализации токенов.\n\treturn normalizeDatePatternForExcel(presetName);\n}\n\nfunction resolveExcelDateFormat(descriptor: TableExcelColumnDescriptor): string {\n\tconst presetName = resolveDatePresetName(descriptor);\n\treturn resolveExcelDateFormatFromPreset(presetName, getDatePreset(presetName));\n}\n\nfunction resolveTableExcelColumnFormat(descriptor: TableExcelColumnDescriptor, type: ExcelColumn[\"type\"]): string | undefined {\n\tif (type === Number) return resolveExcelNumberFormat(descriptor);\n\tif (type === Date) return resolveExcelDateFormat(descriptor);\n\treturn undefined;\n}\n\nexport function createTableExcelColumn(descriptor: TableExcelColumnDescriptor): ExcelColumn {\n\tconst type = resolveTableExcelColumnType(descriptor);\n\treturn {\n\t\tid: descriptor.id,\n\t\theader: descriptor.header,\n\t\twidth: descriptor.width,\n\t\ttype,\n\t\tformat: resolveTableExcelColumnFormat(descriptor, type)\n\t};\n}\n\nexport async function downloadResolvedExcelTable({ fileName, table, autoFilter = true }: DownloadResolvedExcelTableArgs): Promise<void> {\n\tawait downloadExcelFile({\n\t\tfileName,\n\t\tcolumns: table.columns,\n\t\trows: table.rows,\n\t\tautoFilter,\n\t\tgetCellStyle: ({ rowIndex, column }) => table.cellStyles?.[rowIndex]?.[column.id]\n\t});\n}\n\n/** Скачивает многолистовую книгу из таблиц, уже подготовленных доменными consumers. */\nexport async function downloadResolvedExcelWorkbook({ fileName, sheets }: DownloadResolvedExcelWorkbookArgs): Promise<void> {\n\tawait downloadExcelWorkbook({\n\t\tfileName,\n\t\tsheets: sheets.map(({ name, table, autoFilter = true }) => ({\n\t\t\tname,\n\t\t\tcolumns: table.columns,\n\t\t\trows: table.rows,\n\t\t\tautoFilter,\n\t\t\tgetCellStyle: ({ rowIndex, column }) => table.cellStyles?.[rowIndex]?.[column.id]\n\t\t}))\n\t});\n}\n"],"mappings":"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"}
@@ -20,7 +20,25 @@ export type DownloadResolvedExcelTableArgs = {
20
20
  table: ResolvedExcelTable;
21
21
  autoFilter?: boolean;
22
22
  };
23
+ /** Один подготовленный табличный лист generic Excel-книги. */
24
+ export type ResolvedExcelWorkbookSheet = {
25
+ /** Желаемое имя листа. */
26
+ name: string;
27
+ /** Подготовленные колонки, строки и optional стили ячеек. */
28
+ table: ResolvedExcelTable;
29
+ /** Включить автофильтр для этого листа. По умолчанию включён. */
30
+ autoFilter?: boolean;
31
+ };
32
+ /** Аргументы скачивания книги из нескольких подготовленных таблиц. */
33
+ export type DownloadResolvedExcelWorkbookArgs = {
34
+ /** Имя скачиваемого `.xlsx` файла. */
35
+ fileName: string;
36
+ /** Табличные листы в порядке отображения в книге. */
37
+ sheets: ResolvedExcelWorkbookSheet[];
38
+ };
23
39
  export declare function resolveTableExcelColumnType(descriptor: TableExcelColumnDescriptor): ExcelColumn["type"];
24
40
  export declare function createTableExcelColumn(descriptor: TableExcelColumnDescriptor): ExcelColumn;
25
41
  export declare function downloadResolvedExcelTable({ fileName, table, autoFilter }: DownloadResolvedExcelTableArgs): Promise<void>;
42
+ /** Скачивает многолистовую книгу из таблиц, уже подготовленных доменными consumers. */
43
+ export declare function downloadResolvedExcelWorkbook({ fileName, sheets }: DownloadResolvedExcelWorkbookArgs): Promise<void>;
26
44
  //# sourceMappingURL=tableExport.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tableExport.d.ts","sourceRoot":"","sources":["../../src/excel/tableExport.ts"],"names":[],"mappings":"AAEA,OAAO,EAEN,KAAK,wBAAwB,EAE7B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAKN,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAqB,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAElG,MAAM,MAAM,0BAA0B,GAAG;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,kBAAkB,CAAC,EAAE,wBAAwB,CAAC;CAC9C,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAChC,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,UAAU,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;CACjF,CAAC;AAEF,MAAM,MAAM,8BAA8B,GAAG;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAqBF,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,0BAA0B,GAAG,WAAW,CAAC,MAAM,CAAC,CAOvG;AAkFD,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,0BAA0B,GAAG,WAAW,CAS1F;AAED,wBAAsB,0BAA0B,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAiB,EAAE,EAAE,8BAA8B,GAAG,OAAO,CAAC,IAAI,CAAC,CAQtI"}
1
+ {"version":3,"file":"tableExport.d.ts","sourceRoot":"","sources":["../../src/excel/tableExport.ts"],"names":[],"mappings":"AAEA,OAAO,EAEN,KAAK,wBAAwB,EAE7B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAKN,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAA4C,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEzH,MAAM,MAAM,0BAA0B,GAAG;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,kBAAkB,CAAC,EAAE,wBAAwB,CAAC;CAC9C,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAChC,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,UAAU,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;CACjF,CAAC;AAEF,MAAM,MAAM,8BAA8B,GAAG;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,8DAA8D;AAC9D,MAAM,MAAM,0BAA0B,GAAG;IACxC,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,KAAK,EAAE,kBAAkB,CAAC;IAC1B,iEAAiE;IACjE,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,sEAAsE;AACtE,MAAM,MAAM,iCAAiC,GAAG;IAC/C,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,MAAM,EAAE,0BAA0B,EAAE,CAAC;CACrC,CAAC;AAqBF,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,0BAA0B,GAAG,WAAW,CAAC,MAAM,CAAC,CAOvG;AAkFD,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,0BAA0B,GAAG,WAAW,CAS1F;AAED,wBAAsB,0BAA0B,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAiB,EAAE,EAAE,8BAA8B,GAAG,OAAO,CAAC,IAAI,CAAC,CAQtI;AAED,uFAAuF;AACvF,wBAAsB,6BAA6B,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,iCAAiC,GAAG,OAAO,CAAC,IAAI,CAAC,CAW1H"}
@@ -61,10 +61,11 @@ function A(e = 6) {
61
61
  }
62
62
  //#endregion
63
63
  //#region src/hooks/useFloatingListbox.ts
64
- function j({ options: e, selectedIndex: a, open: s, onOpenChange: u, getOptionDisabled: d, onSelect: f, disabled: p, placement: m = "bottom-start", closeOnSelect: w = !0, focusFloatingOnOpen: T = !0, allowOpenWithoutOptions: E = !1, restoreFocusOnClose: D = !0 }) {
65
- let O = c([]), [k, A] = l(!1), j = s ?? k, M = u ?? A, [N, P] = l(a >= 0 ? a : t(e, d)), F = (n) => e.length === 0 ? -1 : n >= 0 && n < e.length && !d?.(e[n]) ? n : a >= 0 && a < e.length && !d?.(e[a]) ? a : t(e, d), I = F(N), { refs: L, floatingStyles: R, context: z } = x({
66
- open: j,
67
- onOpenChange: M,
64
+ var j = /* @__PURE__ */ new WeakMap();
65
+ function M({ options: e, selectedIndex: a, open: s, onOpenChange: u, getOptionDisabled: d, onSelect: f, disabled: p, placement: m = "bottom-start", closeOnSelect: w = !0, focusFloatingOnOpen: T = !0, allowOpenWithoutOptions: E = !1, restoreFocusOnClose: D = !0, resolveFloatingSize: O }) {
66
+ let k = c([]), [A, M] = l(!1), N = s ?? A, P = u ?? M, [F, I] = l(a >= 0 ? a : t(e, d)), L = (n) => e.length === 0 ? -1 : n >= 0 && n < e.length && !d?.(e[n]) ? n : a >= 0 && a < e.length && !d?.(e[a]) ? a : t(e, d), R = L(F), { refs: z, floatingStyles: B, context: V } = x({
67
+ open: N,
68
+ onOpenChange: P,
68
69
  placement: m,
69
70
  transform: !1,
70
71
  strategy: "fixed",
@@ -74,144 +75,171 @@ function j({ options: e, selectedIndex: a, open: s, onOpenChange: u, getOptionDi
74
75
  v({ padding: 8 }),
75
76
  y({
76
77
  padding: 8,
77
- apply({ availableHeight: e, rects: t, elements: n }) {
78
- Object.assign(n.floating.style, {
79
- minWidth: `${t.reference.width}px`,
80
- maxHeight: `${Math.max(e, 120)}px`
78
+ apply({ availableWidth: e, availableHeight: t, rects: n, elements: r }) {
79
+ let i = r.floating.ownerDocument.documentElement, a = r.floating.style, o = O?.({
80
+ availableWidth: e,
81
+ availableHeight: t,
82
+ viewportWidth: i.clientWidth,
83
+ viewportHeight: i.clientHeight,
84
+ referenceWidth: n.reference.width,
85
+ referenceHeight: n.reference.height
86
+ }), s = j.get(r.floating);
87
+ for (let e of s?.customProperties ?? []) a.removeProperty(e);
88
+ for (let [e, t] of s?.standardProperties ?? []) a[e] = t;
89
+ a.minWidth = `${n.reference.width}px`, a.maxHeight = `${Math.max(t, 120)}px`;
90
+ let c = /* @__PURE__ */ new Set(), l = /* @__PURE__ */ new Map();
91
+ for (let [e, t] of Object.entries(o ?? {})) {
92
+ if (e.startsWith("--")) {
93
+ let n = e;
94
+ t !== void 0 && (a.setProperty(n, t), c.add(n));
95
+ continue;
96
+ }
97
+ if (t !== void 0) {
98
+ let n = e;
99
+ l.set(n, a[n]), a[n] = t;
100
+ }
101
+ }
102
+ j.set(r.floating, {
103
+ customProperties: c,
104
+ standardProperties: l
81
105
  });
82
106
  }
83
107
  })
84
108
  ],
85
109
  whileElementsMounted: h
86
- }), { getFloatingProps: B } = S([b(z), C(z, { role: "listbox" })]), V = E || e.length > 0;
110
+ }), { getFloatingProps: H } = S([b(V), C(V, { role: "listbox" })]), U = E || e.length > 0;
87
111
  o(() => {
88
- !j || I < 0 || O.current[I]?.scrollIntoView({ block: "nearest" });
89
- }, [I, j]), o(() => {
90
- !j || !T || requestAnimationFrame(() => {
91
- L.floating.current?.focus();
112
+ !N || R < 0 || k.current[R]?.scrollIntoView({ block: "nearest" });
113
+ }, [R, N]), o(() => {
114
+ !N || !T || requestAnimationFrame(() => {
115
+ z.floating.current?.focus();
92
116
  });
93
117
  }, [
94
118
  T,
95
- j,
96
- L
119
+ N,
120
+ z
97
121
  ]);
98
- let H = () => {
99
- if (M(!1), D) {
100
- let e = L.domReference.current;
122
+ let W = () => {
123
+ if (P(!1), D) {
124
+ let e = z.domReference.current;
101
125
  e instanceof HTMLElement && e.focus();
102
126
  }
103
- }, U = (t) => {
104
- p || !V || (e.length > 0 && P(F(t)), M(!0));
105
- }, W = () => {
106
- U(a >= 0 ? a : t(e, d));
107
- }, G = () => {
108
- if (j) {
109
- H();
127
+ }, G = (t) => {
128
+ p || !U || (e.length > 0 && I(L(t)), P(!0));
129
+ }, K = () => {
130
+ G(a >= 0 ? a : t(e, d));
131
+ }, q = () => {
132
+ if (N) {
133
+ W();
110
134
  return;
111
135
  }
112
- W();
113
- }, K = (e) => {
114
- d?.(e) || (f?.(e), w && H());
115
- }, q = () => {
116
- if (I < 0) return;
117
- let t = e[I];
118
- t !== void 0 && K(t);
119
- }, J = () => {
120
- P(t(e, d));
136
+ K();
137
+ }, J = (e) => {
138
+ d?.(e) || (f?.(e), w && W());
121
139
  }, Y = () => {
122
- P(n(e, d));
123
- }, X = (i) => {
140
+ if (R < 0) return;
141
+ let t = e[R];
142
+ t !== void 0 && J(t);
143
+ }, X = () => {
144
+ I(t(e, d));
145
+ }, Z = () => {
146
+ I(n(e, d));
147
+ }, Q = (i) => {
124
148
  if (!p) {
125
149
  if (i.key === "ArrowDown") {
126
- if (i.preventDefault(), !j) {
127
- U(a >= 0 ? a : t(e, d));
150
+ if (i.preventDefault(), !N) {
151
+ G(a >= 0 ? a : t(e, d));
128
152
  return;
129
153
  }
130
- P((t) => r(e, F(t), 1, d));
154
+ I((t) => r(e, L(t), 1, d));
131
155
  return;
132
156
  }
133
157
  if (i.key === "ArrowUp") {
134
- if (i.preventDefault(), !j) {
135
- U(a >= 0 ? a : n(e, d));
158
+ if (i.preventDefault(), !N) {
159
+ G(a >= 0 ? a : n(e, d));
136
160
  return;
137
161
  }
138
- P((t) => r(e, F(t), -1, d));
162
+ I((t) => r(e, L(t), -1, d));
139
163
  return;
140
164
  }
141
- if (i.key === "Home" && j) {
142
- i.preventDefault(), J();
165
+ if (i.key === "Home" && N) {
166
+ i.preventDefault(), X();
143
167
  return;
144
168
  }
145
- if (i.key === "End" && j) {
146
- i.preventDefault(), Y();
169
+ if (i.key === "End" && N) {
170
+ i.preventDefault(), Z();
147
171
  return;
148
172
  }
149
- if (i.key === "Escape" && j) {
150
- i.preventDefault(), H();
173
+ if (i.key === "Escape" && N) {
174
+ i.preventDefault(), W();
151
175
  return;
152
176
  }
153
- i.key === "Tab" && j && H();
177
+ i.key === "Tab" && N && W();
178
+ }
179
+ }, ee = (t) => {
180
+ if (t.target !== t.currentTarget) {
181
+ t.key === "Escape" && (t.preventDefault(), W());
182
+ return;
154
183
  }
155
- }, Z = (t) => {
156
184
  if (t.key === "ArrowDown") {
157
- t.preventDefault(), P((t) => r(e, F(t), 1, d));
185
+ t.preventDefault(), I((t) => r(e, L(t), 1, d));
158
186
  return;
159
187
  }
160
188
  if (t.key === "ArrowUp") {
161
- t.preventDefault(), P((t) => r(e, F(t), -1, d));
189
+ t.preventDefault(), I((t) => r(e, L(t), -1, d));
162
190
  return;
163
191
  }
164
192
  if (t.key === "Home") {
165
- t.preventDefault(), J();
193
+ t.preventDefault(), X();
166
194
  return;
167
195
  }
168
196
  if (t.key === "End") {
169
- t.preventDefault(), Y();
197
+ t.preventDefault(), Z();
170
198
  return;
171
199
  }
172
- if (!i(t, q)) {
200
+ if (!i(t, Y)) {
173
201
  if (t.key === "Escape") {
174
- t.preventDefault(), H();
202
+ t.preventDefault(), W();
175
203
  return;
176
204
  }
177
- t.key === "Tab" && H();
205
+ t.key === "Tab" && W();
178
206
  }
179
- }, Q = (e, t) => `${e}-option-${t}`;
207
+ }, $ = (e, t) => `${e}-option-${t}`;
180
208
  return {
181
- open: j,
182
- setOpen: M,
183
- activeIndex: I,
184
- refs: L,
185
- context: z,
186
- floatingStyles: R,
187
- getFloatingProps: B,
188
- setReference: L.setReference,
189
- setFloating: L.setFloating,
209
+ open: N,
210
+ setOpen: P,
211
+ activeIndex: R,
212
+ refs: z,
213
+ context: V,
214
+ floatingStyles: B,
215
+ getFloatingProps: H,
216
+ setReference: z.setReference,
217
+ setFloating: z.setFloating,
190
218
  setOptionRef: (e, t) => {
191
- O.current[e] = t;
219
+ k.current[e] = t;
192
220
  },
193
- close: H,
194
- openList: W,
195
- openWithIndex: U,
196
- toggleOpen: G,
197
- selectOption: K,
198
- selectActiveOption: q,
199
- handleReferenceKeyDown: X,
200
- handleFloatingKeyDown: Z,
201
- getOptionId: Q,
202
- getActiveOptionId: (e) => I >= 0 ? Q(e, I) : void 0
221
+ close: W,
222
+ openList: K,
223
+ openWithIndex: G,
224
+ toggleOpen: q,
225
+ selectOption: J,
226
+ selectActiveOption: Y,
227
+ handleReferenceKeyDown: Q,
228
+ handleFloatingKeyDown: ee,
229
+ getOptionId: $,
230
+ getActiveOptionId: (e) => R >= 0 ? $(e, R) : void 0
203
231
  };
204
232
  }
205
233
  //#endregion
206
234
  //#region src/hooks/useLazyComponent.ts
207
- var M = (t, n, r) => s(() => e(t, n, r), [
235
+ var N = (t, n, r) => s(() => e(t, n, r), [
208
236
  r,
209
237
  t,
210
238
  n
211
239
  ]);
212
240
  //#endregion
213
241
  //#region src/hooks/useReferenceTrace.ts
214
- function N(e, t) {
242
+ function P(e, t) {
215
243
  let n = c(t);
216
244
  o(() => {
217
245
  if (!__DEV__) return;
@@ -225,7 +253,7 @@ function N(e, t) {
225
253
  }
226
254
  //#endregion
227
255
  //#region src/hooks/useSearchLink.ts
228
- function P() {
256
+ function F() {
229
257
  let e = w();
230
258
  return { buildLink: (t) => {
231
259
  let n = new URLSearchParams(e.searchStr);
@@ -240,7 +268,7 @@ function P() {
240
268
  }
241
269
  //#endregion
242
270
  //#region src/hooks/useSearchParams.ts
243
- function F(e) {
271
+ function I(e) {
244
272
  let t = E({ strict: !1 }), n = T();
245
273
  return {
246
274
  params: t,
@@ -257,7 +285,7 @@ function F(e) {
257
285
  }
258
286
  //#endregion
259
287
  //#region src/hooks/useThrottledCallback.ts
260
- function I(e, t, n) {
288
+ function L(e, t, n) {
261
289
  let r = c(e);
262
290
  o(() => {
263
291
  r.current = e;
@@ -295,6 +323,6 @@ function I(e, t, n) {
295
323
  };
296
324
  }
297
325
  //#endregion
298
- export { D as useBlockBrowserZoom, O as useDebounce, k as useDebouncedCallback, A as useDndSortableSensors, j as useFloatingListbox, M as useLazyComponent, N as useReferenceTrace, P as useSearchLink, F as useSearchParams, I as useThrottledCallback };
326
+ export { D as useBlockBrowserZoom, O as useDebounce, k as useDebouncedCallback, A as useDndSortableSensors, M as useFloatingListbox, N as useLazyComponent, P as useReferenceTrace, F as useSearchLink, I as useSearchParams, L as useThrottledCallback };
299
327
 
300
328
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/hooks/useBlockBrowserZoom.ts","../../src/hooks/useDebounce.ts","../../src/hooks/useDebounceCallback.ts","../../src/hooks/useDndSortableSensors.ts","../../src/hooks/useFloatingListbox.ts","../../src/hooks/useLazyComponent.ts","../../src/hooks/useReferenceTrace.ts","../../src/hooks/useSearchLink.ts","../../src/hooks/useSearchParams.ts","../../src/hooks/useThrottledCallback.ts"],"sourcesContent":["import { useEffect } from \"react\";\n\n/**\n * Глобально отключает браузерный zoom (ctrl+wheel).\n *\n * @param enabled если true — блокируем нативный зум страницы\n */\nexport function useBlockBrowserZoom(enabled: boolean) {\n\tuseEffect(() => {\n\t\tif (!enabled) return;\n\n\t\tconst handler = (e: WheelEvent) => {\n\t\t\tif (e.ctrlKey) {\n\t\t\t\te.preventDefault();\n\t\t\t}\n\t\t};\n\n\t\twindow.addEventListener(\"wheel\", handler, { passive: false });\n\t\treturn () => window.removeEventListener(\"wheel\", handler);\n\t}, [enabled]);\n}\n","import { useEffect, useState } from \"react\";\n\nexport function useDebounce<T>(value: T, delay: number): T {\n\tconst [debouncedValue, setDebouncedValue] = useState<T>(value);\n\n\tuseEffect(() => {\n\t\tconst timeoutId = setTimeout(() => {\n\t\t\tsetDebouncedValue(value);\n\t\t}, delay);\n\n\t\treturn () => clearTimeout(timeoutId);\n\t}, [value, delay]);\n\n\treturn debouncedValue;\n}\n","import { useCallback, useEffect, useRef } from \"react\";\n\n/**\n * Дебаунсит вызов колбэка: частые вызовы `call(value)` не вызывают `cb` сразу,\n * а откладывают его на `delayMs` после последнего вызова.\n *\n * Важно:\n * - Всегда сохраняется *последнее* значение (last-write-wins).\n * - `flush()` немедленно вызывает cb с последним сохранённым значением (если оно есть).\n * - `cancel()` отменяет таймер и забывает последнее значение.\n * - На unmount автоматически вызывается `cancel()` чтобы не было setState после размонтирования.\n *\n * Типовой кейс:\n * - onNodesChange -> call(snapshot)\n * - pointerup / blur / unmount -> flush() (или cancel(), если не надо сохранять)\n */\nexport function useDebouncedCallback<T>(cb: (arg: T) => void, delayMs: number, options?: { flushOnUnmount?: boolean }) {\n\t/**\n\t * Храним актуальную версию cb в ref, чтобы:\n\t * - не пересоздавать дебаунсер при каждом рендере,\n\t * - не ловить устаревший замыканием cb (stale closure),\n\t * - при этом всегда вызывать самую свежую функцию.\n\t */\n\tconst cbRef = useRef<(arg: T) => void>(cb);\n\n\t/**\n\t * Обновляем актуальную версию cb после commit'а\n\t */\n\tuseEffect(() => {\n\t\tcbRef.current = cb;\n\t}, [cb]);\n\n\t/**\n\t * id таймера. null означает что таймер не активен.\n\t */\n\tconst tRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n\t/**\n\t * Последнее значение, которое мы хотим \"закоммитить\" в cb.\n\t * Используем стратегию last-write-wins.\n\t */\n\tconst lastRef = useRef<T | null>(null);\n\n\t/**\n\t * cancel():\n\t * - останавливаем таймер, если он был,\n\t * - очищаем последнее значение (ничего не будет вызвано).\n\t */\n\tconst cancel = useCallback(() => {\n\t\tif (tRef.current) {\n\t\t\tclearTimeout(tRef.current);\n\t\t\ttRef.current = null;\n\t\t}\n\t\tlastRef.current = null;\n\t}, []);\n\n\t/**\n\t * flush():\n\t * - если есть отложенное значение, вызываем cb немедленно,\n\t * - сбрасываем таймер и lastRef.\n\t *\n\t * Используется, когда нужно гарантированно сохранить \"последнее\" прямо сейчас:\n\t * - unmount\n\t * - visibilitychange/blur\n\t * - pointerup/mouseup (конец взаимодействия)\n\t */\n\tconst flush = useCallback(() => {\n\t\tconst v = lastRef.current;\n\t\tif (v === null) return;\n\n\t\t// Сбрасываем before-call, чтобы если cb вызовет call() рекурсивно,\n\t\t// мы не держали старое значение.\n\t\tlastRef.current = null;\n\n\t\tif (tRef.current) {\n\t\t\tclearTimeout(tRef.current);\n\t\t\ttRef.current = null;\n\t\t}\n\n\t\tcbRef.current(v);\n\t}, []);\n\n\t/**\n\t * call(arg):\n\t * - сохраняем arg как \"последнее значение\"\n\t * - перезапускаем таймер\n\t * - по истечении delayMs вызываем flush()\n\t */\n\tconst call = useCallback(\n\t\t(arg: T) => {\n\t\t\tlastRef.current = arg;\n\n\t\t\t// Перезапуск таймера -> классический debounce\n\t\t\tif (tRef.current) clearTimeout(tRef.current);\n\n\t\t\ttRef.current = setTimeout(() => {\n\t\t\t\ttRef.current = null;\n\t\t\t\tflush();\n\t\t\t}, delayMs);\n\t\t},\n\t\t[delayMs, flush]\n\t);\n\n\t/**\n\t * Авто-cancel на unmount:\n\t * - убирает pending timeout\n\t * - предотвращает вызовы cb после размонтирования компонента\n\t */\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tif (options?.flushOnUnmount) {\n\t\t\t\tflush();\n\t\t\t} else {\n\t\t\t\tcancel();\n\t\t\t}\n\t\t};\n\t}, [cancel, flush, options?.flushOnUnmount]);\n\n\treturn { call, flush, cancel };\n}\n","import { KeyboardSensor, PointerSensor, useSensor, useSensors } from \"@dnd-kit/core\";\nimport { sortableKeyboardCoordinates } from \"@dnd-kit/sortable\";\n\nexport interface UseDndSortableSensorsOptions {\n\tactivationDistance?: number;\n}\n\n/**\n * Стандартная конфигурация DnD-сенсоров для сортируемых списков.\n *\n * Зачем: исключить дублирование одинаковой настройки PointerSensor + KeyboardSensor\n * в каждом хуке персонализации (колонки, сортировки, группировки).\n *\n * @param distance — минимальное смещение указателя (px) для начала перетаскивания (по умолчанию 6).\n */\nexport function useDndSortableSensors(distanceOrOptions: number | UseDndSortableSensorsOptions = 6) {\n\tconst distance = typeof distanceOrOptions === \"number\" ? distanceOrOptions : (distanceOrOptions.activationDistance ?? 6);\n\n\treturn useSensors(\n\t\tuseSensor(PointerSensor, { activationConstraint: { distance } }),\n\t\tuseSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })\n\t);\n}\n","import { useEffect, useRef, useState } from \"react\";\n\nimport {\n\tautoUpdate,\n\tflip,\n\tsize as floatingSize,\n\toffset,\n\tPlacement,\n\tshift,\n\tuseDismiss,\n\tuseFloating,\n\tuseInteractions,\n\tuseRole\n} from \"@floating-ui/react\";\n\nimport { findFirstEnabledIndex, findLastEnabledIndex, findNextEnabledIndex, handleKeyboardActivation } from \"../utils\";\n\ninterface UseFloatingListboxParams<T> {\n\toptions: readonly T[];\n\tselectedIndex: number;\n\topen?: boolean;\n\tonOpenChange?: (open: boolean) => void;\n\tgetOptionDisabled?: (option: T) => boolean;\n\tonSelect?: (option: T) => void;\n\tdisabled?: boolean;\n\tplacement?: Placement;\n\tcloseOnSelect?: boolean;\n\tfocusFloatingOnOpen?: boolean;\n\tallowOpenWithoutOptions?: boolean;\n\trestoreFocusOnClose?: boolean;\n}\n\nexport function useFloatingListbox<T>({\n\toptions,\n\tselectedIndex,\n\topen,\n\tonOpenChange,\n\tgetOptionDisabled,\n\tonSelect,\n\tdisabled,\n\tplacement = \"bottom-start\",\n\tcloseOnSelect = true,\n\tfocusFloatingOnOpen = true,\n\tallowOpenWithoutOptions = false,\n\trestoreFocusOnClose = true\n}: UseFloatingListboxParams<T>) {\n\tconst optionRefs = useRef<Array<HTMLElement | null>>([]);\n\tconst [internalOpen, setInternalOpen] = useState(false);\n\tconst resolvedOpen = open ?? internalOpen;\n\tconst setResolvedOpen = onOpenChange ?? setInternalOpen;\n\tconst [currentActiveIndex, setActiveIndex] = useState(\n\t\tselectedIndex >= 0 ? selectedIndex : findFirstEnabledIndex(options, getOptionDisabled)\n\t);\n\n\t/**\n\t * Поддерживает активный индекс в валидном состоянии при изменении набора опций\n\t * или выбранного элемента, чтобы `aria-activedescendant` всегда ссылался на\n\t * существующую и доступную опцию.\n\t */\n\tconst resolveActiveIndex = (activeIndex: number) => {\n\t\tif (options.length === 0) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (activeIndex >= 0 && activeIndex < options.length && !getOptionDisabled?.(options[activeIndex])) {\n\t\t\treturn activeIndex;\n\t\t}\n\n\t\tif (selectedIndex >= 0 && selectedIndex < options.length && !getOptionDisabled?.(options[selectedIndex])) {\n\t\t\treturn selectedIndex;\n\t\t}\n\n\t\treturn findFirstEnabledIndex(options, getOptionDisabled);\n\t};\n\tconst activeIndex = resolveActiveIndex(currentActiveIndex);\n\n\tconst { refs, floatingStyles, context } = useFloating({\n\t\topen: resolvedOpen,\n\t\tonOpenChange: setResolvedOpen,\n\t\tplacement,\n\t\ttransform: false,\n\t\tstrategy: \"fixed\",\n\t\tmiddleware: [\n\t\t\toffset(4),\n\t\t\tflip({ padding: 8 }),\n\t\t\tshift({ padding: 8 }),\n\t\t\tfloatingSize({\n\t\t\t\tpadding: 8,\n\t\t\t\tapply({ availableHeight, rects, elements }) {\n\t\t\t\t\tObject.assign(elements.floating.style, {\n\t\t\t\t\t\tminWidth: `${rects.reference.width}px`,\n\t\t\t\t\t\tmaxHeight: `${Math.max(availableHeight, 120)}px`\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t})\n\t\t],\n\t\twhileElementsMounted: autoUpdate\n\t});\n\n\tconst dismiss = useDismiss(context);\n\tconst role = useRole(context, { role: \"listbox\" });\n\tconst { getFloatingProps } = useInteractions([dismiss, role]);\n\n\tconst canOpen = allowOpenWithoutOptions || options.length > 0;\n\n\tuseEffect(() => {\n\t\tif (!resolvedOpen || activeIndex < 0) {\n\t\t\treturn;\n\t\t}\n\n\t\toptionRefs.current[activeIndex]?.scrollIntoView({ block: \"nearest\" });\n\t}, [activeIndex, resolvedOpen]);\n\n\tuseEffect(() => {\n\t\tif (!resolvedOpen || !focusFloatingOnOpen) {\n\t\t\treturn;\n\t\t}\n\n\t\trequestAnimationFrame(() => {\n\t\t\trefs.floating.current?.focus();\n\t\t});\n\t}, [focusFloatingOnOpen, resolvedOpen, refs]);\n\n\tconst close = () => {\n\t\tsetResolvedOpen(false);\n\n\t\tif (restoreFocusOnClose) {\n\t\t\tconst referenceElement = refs.domReference.current;\n\t\t\tif (referenceElement instanceof HTMLElement) {\n\t\t\t\treferenceElement.focus();\n\t\t\t}\n\t\t}\n\t};\n\n\tconst openWithIndex = (nextIndex: number) => {\n\t\tif (disabled || !canOpen) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (options.length > 0) {\n\t\t\tsetActiveIndex(resolveActiveIndex(nextIndex));\n\t\t}\n\n\t\tsetResolvedOpen(true);\n\t};\n\n\tconst openList = () => {\n\t\topenWithIndex(selectedIndex >= 0 ? selectedIndex : findFirstEnabledIndex(options, getOptionDisabled));\n\t};\n\n\tconst toggleOpen = () => {\n\t\tif (resolvedOpen) {\n\t\t\tclose();\n\t\t\treturn;\n\t\t}\n\n\t\topenList();\n\t};\n\n\tconst selectOption = (option: T) => {\n\t\tif (getOptionDisabled?.(option)) {\n\t\t\treturn;\n\t\t}\n\n\t\tonSelect?.(option);\n\n\t\tif (closeOnSelect) {\n\t\t\tclose();\n\t\t}\n\t};\n\n\tconst selectActiveOption = () => {\n\t\tif (activeIndex < 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst activeOption = options[activeIndex];\n\t\tif (activeOption !== undefined) {\n\t\t\tselectOption(activeOption);\n\t\t}\n\t};\n\n\tconst moveActiveToFirst = () => {\n\t\tsetActiveIndex(findFirstEnabledIndex(options, getOptionDisabled));\n\t};\n\n\tconst moveActiveToLast = () => {\n\t\tsetActiveIndex(findLastEnabledIndex(options, getOptionDisabled));\n\t};\n\n\tconst handleReferenceKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {\n\t\tif (disabled) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === \"ArrowDown\") {\n\t\t\tevent.preventDefault();\n\n\t\t\tif (!resolvedOpen) {\n\t\t\t\topenWithIndex(selectedIndex >= 0 ? selectedIndex : findFirstEnabledIndex(options, getOptionDisabled));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tsetActiveIndex((currentIndex) => findNextEnabledIndex(options, resolveActiveIndex(currentIndex), 1, getOptionDisabled));\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === \"ArrowUp\") {\n\t\t\tevent.preventDefault();\n\n\t\t\tif (!resolvedOpen) {\n\t\t\t\topenWithIndex(selectedIndex >= 0 ? selectedIndex : findLastEnabledIndex(options, getOptionDisabled));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tsetActiveIndex((currentIndex) => findNextEnabledIndex(options, resolveActiveIndex(currentIndex), -1, getOptionDisabled));\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === \"Home\" && resolvedOpen) {\n\t\t\tevent.preventDefault();\n\t\t\tmoveActiveToFirst();\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === \"End\" && resolvedOpen) {\n\t\t\tevent.preventDefault();\n\t\t\tmoveActiveToLast();\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === \"Escape\" && resolvedOpen) {\n\t\t\tevent.preventDefault();\n\t\t\tclose();\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === \"Tab\" && resolvedOpen) {\n\t\t\tclose();\n\t\t}\n\t};\n\n\tconst handleFloatingKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {\n\t\tif (event.key === \"ArrowDown\") {\n\t\t\tevent.preventDefault();\n\t\t\tsetActiveIndex((currentIndex) => findNextEnabledIndex(options, resolveActiveIndex(currentIndex), 1, getOptionDisabled));\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === \"ArrowUp\") {\n\t\t\tevent.preventDefault();\n\t\t\tsetActiveIndex((currentIndex) => findNextEnabledIndex(options, resolveActiveIndex(currentIndex), -1, getOptionDisabled));\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === \"Home\") {\n\t\t\tevent.preventDefault();\n\t\t\tmoveActiveToFirst();\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === \"End\") {\n\t\t\tevent.preventDefault();\n\t\t\tmoveActiveToLast();\n\t\t\treturn;\n\t\t}\n\n\t\tif (handleKeyboardActivation(event, selectActiveOption)) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === \"Escape\") {\n\t\t\tevent.preventDefault();\n\t\t\tclose();\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === \"Tab\") {\n\t\t\tclose();\n\t\t}\n\t};\n\n\tconst getOptionId = (listId: string, index: number) => `${listId}-option-${index}`;\n\tconst getActiveOptionId = (listId: string) => (activeIndex >= 0 ? getOptionId(listId, activeIndex) : undefined);\n\n\treturn {\n\t\topen: resolvedOpen,\n\t\tsetOpen: setResolvedOpen,\n\t\tactiveIndex,\n\t\trefs,\n\t\tcontext,\n\t\tfloatingStyles,\n\t\tgetFloatingProps,\n\t\tsetReference: refs.setReference,\n\t\tsetFloating: refs.setFloating,\n\t\tsetOptionRef: (index: number, node: HTMLElement | null) => {\n\t\t\toptionRefs.current[index] = node;\n\t\t},\n\t\tclose,\n\t\topenList,\n\t\topenWithIndex,\n\t\ttoggleOpen,\n\t\tselectOption,\n\t\tselectActiveOption,\n\t\thandleReferenceKeyDown,\n\t\thandleFloatingKeyDown,\n\t\tgetOptionId,\n\t\tgetActiveOptionId\n\t};\n}\n","import { type ComponentType, useMemo } from \"react\";\n\nimport { createLazyComponent } from \"../utils/createLazyComponent\";\n\n/**\n * Хук для создания ленивой компоненты, которая будет загружена только при первом рендере.\n *\n * @example\n *\n * ```tsx\n * const MyComponent = () => {\n * const TextEditor = useLazyComponent(\n * () => import(\"@components/content/article/editor/TextEditor\"),\n * \"TextEditor\"\n * );\n *\n * return (\n * <Suspense fallback={<div>Загрузка...</div>}>\n * <TextEditor />\n * </Suspense>\n * );\n * };\n * ```\n *\n * @param importFn - функция, которая возвращает промис, который резолвится в объект с компонентами\n * @param componentName - имя компонента, который нужно загрузить\n * @returns\n */\nconst useLazyComponent = <TProps extends object>(\n\timportFn: () => Promise<Record<string, ComponentType<TProps>> | null>,\n\tcomponentName: string,\n\tcacheKey?: string\n) => {\n\treturn useMemo(() => createLazyComponent(importFn, componentName, cacheKey), [cacheKey, importFn, componentName]);\n};\n\nexport { useLazyComponent };\n","import { useEffect, useRef } from \"react\";\n\nexport function useReferenceTrace<T>(name: string, value: T) {\n\tconst previousRef = useRef(value);\n\n\tuseEffect(() => {\n\t\tif (!__DEV__) return;\n\n\t\tconst referenceChanged = !Object.is(previousRef.current, value);\n\n\t\tconsole.debug(`[render trace] ${name}`, { referenceChanged, previous: previousRef.current, current: value });\n\n\t\tpreviousRef.current = value;\n\t});\n}\n","import { useLocation } from \"@tanstack/react-router\";\n\nimport { SearchParams } from \"./types\";\n\nexport function useSearchLink<K extends PropertyKey>() {\n\tconst location = useLocation();\n\n\tconst buildLink = (params: SearchParams<K>) => {\n\t\tconst next = new URLSearchParams(location.searchStr);\n\n\t\tparams = params || [];\n\n\t\tfor (const param in params) {\n\t\t\tconst value = params[param];\n\n\t\t\tif (value) {\n\t\t\t\tnext.set(param, String(value));\n\t\t\t} else {\n\t\t\t\tnext.delete(param);\n\t\t\t}\n\t\t}\n\n\t\tconst searchString = next.toString();\n\t\treturn `${location.pathname}${searchString ? `?${searchString}` : \"\"}${location.hash}`;\n\t};\n\n\treturn { buildLink };\n}\n","import { useMemo } from \"react\";\n\nimport { AnyRouter, useNavigate, useSearch } from \"@tanstack/react-router\";\n\nimport { SearchParams, SearchParamsUpdater } from \"./types\";\n\nexport function useSearchParams<K extends PropertyKey>(allowedKeys?: (keyof K)[]) {\n\tconst params = useSearch<AnyRouter, string, false, true, SearchParams<K>>({ strict: false });\n\tconst navigate = useNavigate();\n\n\t// Фильтруем параметры, оставляя только разрешенные ключи\n\tconst filteredParams = useMemo(() => {\n\t\tif (!allowedKeys) return params;\n\n\t\treturn Object.fromEntries(Object.entries(params).filter(([key]) => allowedKeys.includes(key as keyof K))) as SearchParams<K>;\n\t}, [params, allowedKeys]);\n\n\tconst setParams = (next: SearchParamsUpdater<K>) =>\n\t\tnavigate({\n\t\t\tto: \".\",\n\t\t\tsearch: (prev: SearchParams<K>) => ({\n\t\t\t\t...prev,\n\t\t\t\t...next\n\t\t\t}),\n\t\t\treplace: true\n\t\t});\n\n\treturn { params, setParams, filteredParams };\n}\n","import { useCallback, useEffect, useRef } from \"react\";\n\nexport type ThrottledCallback<T> = {\n\t/**\n\t * Основной вход: вызывай на каждом частом событии (например, onNodesChange).\n\t * Колбэк `cb` будет вызываться не чаще, чем 1 раз в `intervalMs`.\n\t *\n\t * Стратегия: last-write-wins — если за интервал пришло много значений,\n\t * по окончании интервала будет применено последнее.\n\t */\n\tcall: (arg: T) => void;\n\n\t/**\n\t * Немедленно применить последнее накопленное значение (если оно есть),\n\t * отменив запланированный вызов (если он был).\n\t * Полезно на:\n\t * - pointerup/mouseup (конец перетаскивания)\n\t * - blur/visibilitychange/beforeunload\n\t * - unmount (если хочешь гарантированно сохранить финальное состояние)\n\t */\n\tflush: () => void;\n\n\t/**\n\t * Отменить запланированный вызов и забыть накопленное значение.\n\t * Полезно, если ты переключаешь редактор/граф и не хочешь “долетов” старых данных.\n\t */\n\tcancel: () => void;\n};\n\n/**\n * useThrottledCallback (React 19 safe)\n *\n * Проблема:\n * - В xyflow onNodesChange/onEdgesChange может сыпаться очень часто.\n * - Если каждый раз писать “сериализуемый снапшот” в zustand → лишние ререндеры/CPU/GC.\n *\n * Решение:\n * - throttle: вызывать cb не чаще, чем 1 раз в intervalMs.\n * - last-write-wins: если за интервал пришло 50 обновлений — применим последнее.\n *\n * React 19 нюанс:\n * - нельзя мутировать ref.current во время render (линтер/правило).\n * - поэтому cbRef обновляем в useEffect.\n */\nexport function useThrottledCallback<T>(\n\tcb: (arg: T) => void,\n\tintervalMs: number,\n\toptions?: { flushOnUnmount?: boolean }\n): ThrottledCallback<T> {\n\t/**\n\t * Актуальная версия cb (обновляется только после commit’а).\n\t * Это решает \"stale closure\": call/flush/cancel стабильны,\n\t * но всегда дергают самый свежий cb.\n\t */\n\tconst cbRef = useRef<(arg: T) => void>(cb);\n\n\tuseEffect(() => {\n\t\tcbRef.current = cb;\n\t}, [cb]);\n\n\t/**\n\t * Таймер отложенного выполнения.\n\t * Он нужен, когда call() пришёл раньше, чем завершился текущий throttle-интервал:\n\t * мы планируем “выполнить в конце окна”.\n\t */\n\tconst tRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n\t/**\n\t * Накопленное “последнее значение”.\n\t * Всегда хранится последний arg, пришедший через call().\n\t */\n\tconst lastRef = useRef<T | null>(null);\n\n\t/**\n\t * Время (timestamp) последнего РЕАЛЬНОГО вызова cb.\n\t * Нужно, чтобы вычислять: можно ли выполнить сейчас или нужно подождать.\n\t */\n\tconst lastInvokeTsRef = useRef<number>(0);\n\n\t/**\n\t * cancel():\n\t * - отменяем отложенный таймер,\n\t * - забываем накопленное значение.\n\t * Важно: это не вызывает cb.\n\t */\n\tconst cancel = useCallback(() => {\n\t\tif (tRef.current) {\n\t\t\tclearTimeout(tRef.current);\n\t\t\ttRef.current = null;\n\t\t}\n\t\tlastRef.current = null;\n\t}, []);\n\n\t/**\n\t * flush():\n\t * - если есть накопленное значение, вызываем cb немедленно,\n\t * - чистим таймер,\n\t * - фиксируем lastInvokeTsRef (чтобы окно throttle считалось от flush тоже).\n\t */\n\tconst flush = useCallback(() => {\n\t\tconst v = lastRef.current;\n\t\tif (v === null) return;\n\n\t\t// Сбрасываем накопленное ДО вызова cb на случай,\n\t\t// если cb внутри синхронно вызовет call() снова.\n\t\tlastRef.current = null;\n\n\t\tif (tRef.current) {\n\t\t\tclearTimeout(tRef.current);\n\t\t\ttRef.current = null;\n\t\t}\n\n\t\tlastInvokeTsRef.current = Date.now();\n\t\tcbRef.current(v);\n\t}, []);\n\n\t/**\n\t * call(arg):\n\t * 1) сохраняем arg как последнее значение (last-write-wins)\n\t * 2) если с момента последнего вызова прошло достаточно времени → вызываем сразу\n\t * 3) иначе — планируем flush ровно в момент, когда throttle-окно закончится\n\t *\n\t * При этом мы гарантируем:\n\t * - не более одного cb вызова за intervalMs\n\t * - и что “последнее значение за интервал” будет применено в конце.\n\t */\n\tconst call = useCallback(\n\t\t(arg: T) => {\n\t\t\tlastRef.current = arg;\n\n\t\t\tconst now = Date.now();\n\t\t\tconst elapsed = now - lastInvokeTsRef.current;\n\n\t\t\t// Если окно уже прошло — можно выполнить немедленно.\n\t\t\tif (elapsed >= intervalMs) {\n\t\t\t\t// Немедленный вызов = “leading edge”.\n\t\t\t\t// Берём последнее значение, очищаем lastRef и вызываем cb.\n\t\t\t\tconst v = lastRef.current;\n\t\t\t\tlastRef.current = null;\n\n\t\t\t\t// На всякий случай прибираем таймер, если он был.\n\t\t\t\tif (tRef.current) {\n\t\t\t\t\tclearTimeout(tRef.current);\n\t\t\t\t\ttRef.current = null;\n\t\t\t\t}\n\n\t\t\t\tlastInvokeTsRef.current = now;\n\t\t\t\tif (v !== null) cbRef.current(v);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Иначе — “trailing edge”: планируем выполнение в конце окна.\n\t\t\t// Важно: если таймер уже запланирован — НЕ пересоздаём,\n\t\t\t// потому что trailing-вызов должен случиться в конкретный момент (конец окна),\n\t\t\t// а значение мы и так обновляем через lastRef.current.\n\t\t\tif (!tRef.current) {\n\t\t\t\tconst wait = intervalMs - elapsed;\n\n\t\t\t\ttRef.current = setTimeout(() => {\n\t\t\t\t\ttRef.current = null;\n\t\t\t\t\t// В конце окна применяем последнее накопленное значение\n\t\t\t\t\tflush();\n\t\t\t\t}, wait);\n\t\t\t}\n\t\t},\n\t\t[intervalMs, flush]\n\t);\n\n\t/**\n\t * Cleanup на unmount:\n\t * - по умолчанию cancel (не делаем побочных эффектов при размонтировании)\n\t * - при flushOnUnmount=true — пытаемся сохранить последнее значение.\n\t *\n\t * Для твоего кейса (сериализация финального snapshot) чаще полезно flushOnUnmount=true.\n\t */\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tif (options?.flushOnUnmount) flush();\n\t\t\telse cancel();\n\t\t};\n\t}, [cancel, flush, options?.flushOnUnmount]);\n\n\treturn { call, flush, cancel };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/hooks/useBlockBrowserZoom.ts","../../src/hooks/useDebounce.ts","../../src/hooks/useDebounceCallback.ts","../../src/hooks/useDndSortableSensors.ts","../../src/hooks/useFloatingListbox.ts","../../src/hooks/useLazyComponent.ts","../../src/hooks/useReferenceTrace.ts","../../src/hooks/useSearchLink.ts","../../src/hooks/useSearchParams.ts","../../src/hooks/useThrottledCallback.ts"],"sourcesContent":["import { useEffect } from \"react\";\n\n/**\n * Глобально отключает браузерный zoom (ctrl+wheel).\n *\n * @param enabled если true — блокируем нативный зум страницы\n */\nexport function useBlockBrowserZoom(enabled: boolean) {\n\tuseEffect(() => {\n\t\tif (!enabled) return;\n\n\t\tconst handler = (e: WheelEvent) => {\n\t\t\tif (e.ctrlKey) {\n\t\t\t\te.preventDefault();\n\t\t\t}\n\t\t};\n\n\t\twindow.addEventListener(\"wheel\", handler, { passive: false });\n\t\treturn () => window.removeEventListener(\"wheel\", handler);\n\t}, [enabled]);\n}\n","import { useEffect, useState } from \"react\";\n\nexport function useDebounce<T>(value: T, delay: number): T {\n\tconst [debouncedValue, setDebouncedValue] = useState<T>(value);\n\n\tuseEffect(() => {\n\t\tconst timeoutId = setTimeout(() => {\n\t\t\tsetDebouncedValue(value);\n\t\t}, delay);\n\n\t\treturn () => clearTimeout(timeoutId);\n\t}, [value, delay]);\n\n\treturn debouncedValue;\n}\n","import { useCallback, useEffect, useRef } from \"react\";\n\n/**\n * Дебаунсит вызов колбэка: частые вызовы `call(value)` не вызывают `cb` сразу,\n * а откладывают его на `delayMs` после последнего вызова.\n *\n * Важно:\n * - Всегда сохраняется *последнее* значение (last-write-wins).\n * - `flush()` немедленно вызывает cb с последним сохранённым значением (если оно есть).\n * - `cancel()` отменяет таймер и забывает последнее значение.\n * - На unmount автоматически вызывается `cancel()` чтобы не было setState после размонтирования.\n *\n * Типовой кейс:\n * - onNodesChange -> call(snapshot)\n * - pointerup / blur / unmount -> flush() (или cancel(), если не надо сохранять)\n */\nexport function useDebouncedCallback<T>(cb: (arg: T) => void, delayMs: number, options?: { flushOnUnmount?: boolean }) {\n\t/**\n\t * Храним актуальную версию cb в ref, чтобы:\n\t * - не пересоздавать дебаунсер при каждом рендере,\n\t * - не ловить устаревший замыканием cb (stale closure),\n\t * - при этом всегда вызывать самую свежую функцию.\n\t */\n\tconst cbRef = useRef<(arg: T) => void>(cb);\n\n\t/**\n\t * Обновляем актуальную версию cb после commit'а\n\t */\n\tuseEffect(() => {\n\t\tcbRef.current = cb;\n\t}, [cb]);\n\n\t/**\n\t * id таймера. null означает что таймер не активен.\n\t */\n\tconst tRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n\t/**\n\t * Последнее значение, которое мы хотим \"закоммитить\" в cb.\n\t * Используем стратегию last-write-wins.\n\t */\n\tconst lastRef = useRef<T | null>(null);\n\n\t/**\n\t * cancel():\n\t * - останавливаем таймер, если он был,\n\t * - очищаем последнее значение (ничего не будет вызвано).\n\t */\n\tconst cancel = useCallback(() => {\n\t\tif (tRef.current) {\n\t\t\tclearTimeout(tRef.current);\n\t\t\ttRef.current = null;\n\t\t}\n\t\tlastRef.current = null;\n\t}, []);\n\n\t/**\n\t * flush():\n\t * - если есть отложенное значение, вызываем cb немедленно,\n\t * - сбрасываем таймер и lastRef.\n\t *\n\t * Используется, когда нужно гарантированно сохранить \"последнее\" прямо сейчас:\n\t * - unmount\n\t * - visibilitychange/blur\n\t * - pointerup/mouseup (конец взаимодействия)\n\t */\n\tconst flush = useCallback(() => {\n\t\tconst v = lastRef.current;\n\t\tif (v === null) return;\n\n\t\t// Сбрасываем before-call, чтобы если cb вызовет call() рекурсивно,\n\t\t// мы не держали старое значение.\n\t\tlastRef.current = null;\n\n\t\tif (tRef.current) {\n\t\t\tclearTimeout(tRef.current);\n\t\t\ttRef.current = null;\n\t\t}\n\n\t\tcbRef.current(v);\n\t}, []);\n\n\t/**\n\t * call(arg):\n\t * - сохраняем arg как \"последнее значение\"\n\t * - перезапускаем таймер\n\t * - по истечении delayMs вызываем flush()\n\t */\n\tconst call = useCallback(\n\t\t(arg: T) => {\n\t\t\tlastRef.current = arg;\n\n\t\t\t// Перезапуск таймера -> классический debounce\n\t\t\tif (tRef.current) clearTimeout(tRef.current);\n\n\t\t\ttRef.current = setTimeout(() => {\n\t\t\t\ttRef.current = null;\n\t\t\t\tflush();\n\t\t\t}, delayMs);\n\t\t},\n\t\t[delayMs, flush]\n\t);\n\n\t/**\n\t * Авто-cancel на unmount:\n\t * - убирает pending timeout\n\t * - предотвращает вызовы cb после размонтирования компонента\n\t */\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tif (options?.flushOnUnmount) {\n\t\t\t\tflush();\n\t\t\t} else {\n\t\t\t\tcancel();\n\t\t\t}\n\t\t};\n\t}, [cancel, flush, options?.flushOnUnmount]);\n\n\treturn { call, flush, cancel };\n}\n","import { KeyboardSensor, PointerSensor, useSensor, useSensors } from \"@dnd-kit/core\";\nimport { sortableKeyboardCoordinates } from \"@dnd-kit/sortable\";\n\nexport interface UseDndSortableSensorsOptions {\n\tactivationDistance?: number;\n}\n\n/**\n * Стандартная конфигурация DnD-сенсоров для сортируемых списков.\n *\n * Зачем: исключить дублирование одинаковой настройки PointerSensor + KeyboardSensor\n * в каждом хуке персонализации (колонки, сортировки, группировки).\n *\n * @param distance — минимальное смещение указателя (px) для начала перетаскивания (по умолчанию 6).\n */\nexport function useDndSortableSensors(distanceOrOptions: number | UseDndSortableSensorsOptions = 6) {\n\tconst distance = typeof distanceOrOptions === \"number\" ? distanceOrOptions : (distanceOrOptions.activationDistance ?? 6);\n\n\treturn useSensors(\n\t\tuseSensor(PointerSensor, { activationConstraint: { distance } }),\n\t\tuseSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })\n\t);\n}\n","import { useEffect, useRef, useState } from \"react\";\n\nimport {\n\tautoUpdate,\n\tflip,\n\tsize as floatingSize,\n\toffset,\n\tPlacement,\n\tshift,\n\tuseDismiss,\n\tuseFloating,\n\tuseInteractions,\n\tuseRole\n} from \"@floating-ui/react\";\n\nimport { findFirstEnabledIndex, findLastEnabledIndex, findNextEnabledIndex, handleKeyboardActivation } from \"../utils\";\n\n/** Геометрия, которую Floating UI вычислил для доступной стороны listbox. */\nexport type FloatingListboxSizeContext = {\n\tavailableWidth: number;\n\tavailableHeight: number;\n\tviewportWidth: number;\n\tviewportHeight: number;\n\treferenceWidth: number;\n\treferenceHeight: number;\n};\n\n/**\n * Размеры floating-элемента и CSS custom properties, которые специализированный\n * picker может вычислить из доступного viewport без собственного middleware.\n */\nexport type FloatingListboxSizeStyle = {\n\twidth?: string;\n\tminWidth?: string;\n\tmaxWidth?: string;\n\tmaxHeight?: string;\n} & {\n\t[customProperty: `--${string}`]: string | undefined;\n};\n\nexport type FloatingListboxSizeResolver = (context: FloatingListboxSizeContext) => FloatingListboxSizeStyle;\n\ntype FloatingListboxStandardSizeProperty = \"width\" | \"minWidth\" | \"maxWidth\" | \"maxHeight\";\n\ntype FloatingListboxAppliedSizeState = {\n\tcustomProperties: Set<`--${string}`>;\n\tstandardProperties: Map<FloatingListboxStandardSizeProperty, string>;\n};\n\n/** Хранит свойства и их исходные значения до императивного применения resolver. */\nconst floatingListboxAppliedSizeState = new WeakMap<HTMLElement, FloatingListboxAppliedSizeState>();\n\ninterface UseFloatingListboxParams<T> {\n\toptions: readonly T[];\n\tselectedIndex: number;\n\topen?: boolean;\n\tonOpenChange?: (open: boolean) => void;\n\tgetOptionDisabled?: (option: T) => boolean;\n\tonSelect?: (option: T) => void;\n\tdisabled?: boolean;\n\tplacement?: Placement;\n\tcloseOnSelect?: boolean;\n\tfocusFloatingOnOpen?: boolean;\n\tallowOpenWithoutOptions?: boolean;\n\trestoreFocusOnClose?: boolean;\n\tresolveFloatingSize?: FloatingListboxSizeResolver;\n}\n\nexport function useFloatingListbox<T>({\n\toptions,\n\tselectedIndex,\n\topen,\n\tonOpenChange,\n\tgetOptionDisabled,\n\tonSelect,\n\tdisabled,\n\tplacement = \"bottom-start\",\n\tcloseOnSelect = true,\n\tfocusFloatingOnOpen = true,\n\tallowOpenWithoutOptions = false,\n\trestoreFocusOnClose = true,\n\tresolveFloatingSize\n}: UseFloatingListboxParams<T>) {\n\tconst optionRefs = useRef<Array<HTMLElement | null>>([]);\n\tconst [internalOpen, setInternalOpen] = useState(false);\n\tconst resolvedOpen = open ?? internalOpen;\n\tconst setResolvedOpen = onOpenChange ?? setInternalOpen;\n\tconst [currentActiveIndex, setActiveIndex] = useState(\n\t\tselectedIndex >= 0 ? selectedIndex : findFirstEnabledIndex(options, getOptionDisabled)\n\t);\n\n\t/**\n\t * Поддерживает активный индекс в валидном состоянии при изменении набора опций\n\t * или выбранного элемента, чтобы `aria-activedescendant` всегда ссылался на\n\t * существующую и доступную опцию.\n\t */\n\tconst resolveActiveIndex = (activeIndex: number) => {\n\t\tif (options.length === 0) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (activeIndex >= 0 && activeIndex < options.length && !getOptionDisabled?.(options[activeIndex])) {\n\t\t\treturn activeIndex;\n\t\t}\n\n\t\tif (selectedIndex >= 0 && selectedIndex < options.length && !getOptionDisabled?.(options[selectedIndex])) {\n\t\t\treturn selectedIndex;\n\t\t}\n\n\t\treturn findFirstEnabledIndex(options, getOptionDisabled);\n\t};\n\tconst activeIndex = resolveActiveIndex(currentActiveIndex);\n\n\tconst { refs, floatingStyles, context } = useFloating({\n\t\topen: resolvedOpen,\n\t\tonOpenChange: setResolvedOpen,\n\t\tplacement,\n\t\ttransform: false,\n\t\tstrategy: \"fixed\",\n\t\tmiddleware: [\n\t\t\toffset(4),\n\t\t\tflip({ padding: 8 }),\n\t\t\tshift({ padding: 8 }),\n\t\t\tfloatingSize({\n\t\t\t\tpadding: 8,\n\t\t\t\tapply({ availableWidth, availableHeight, rects, elements }) {\n\t\t\t\t\tconst viewport = elements.floating.ownerDocument.documentElement;\n\t\t\t\t\tconst floatingStyle = elements.floating.style;\n\t\t\t\t\tconst resolvedSize = resolveFloatingSize?.({\n\t\t\t\t\t\tavailableWidth,\n\t\t\t\t\t\tavailableHeight,\n\t\t\t\t\t\tviewportWidth: viewport.clientWidth,\n\t\t\t\t\t\tviewportHeight: viewport.clientHeight,\n\t\t\t\t\t\treferenceWidth: rects.reference.width,\n\t\t\t\t\t\treferenceHeight: rects.reference.height\n\t\t\t\t\t});\n\n\t\t\t\t\t/*\n\t\t\t\t\t * Сначала сбрасываем размеры предыдущего resolver: Floating UI сохраняет\n\t\t\t\t\t * один DOM-узел между режимами, поэтому старые width и CSS-переменные\n\t\t\t\t\t * иначе продолжат влиять на следующий вариант отображения.\n\t\t\t\t\t */\n\t\t\t\t\tconst previousAppliedState = floatingListboxAppliedSizeState.get(elements.floating);\n\t\t\t\t\tfor (const property of previousAppliedState?.customProperties ?? []) {\n\t\t\t\t\t\tfloatingStyle.removeProperty(property);\n\t\t\t\t\t}\n\t\t\t\t\tfor (const [property, previousValue] of previousAppliedState?.standardProperties ?? []) {\n\t\t\t\t\t\tfloatingStyle[property] = previousValue;\n\t\t\t\t\t}\n\n\t\t\t\t\tfloatingStyle.minWidth = `${rects.reference.width}px`;\n\t\t\t\t\tfloatingStyle.maxHeight = `${Math.max(availableHeight, 120)}px`;\n\n\t\t\t\t\tconst nextCustomProperties = new Set<`--${string}`>();\n\t\t\t\t\tconst nextStandardProperties = new Map<FloatingListboxStandardSizeProperty, string>();\n\t\t\t\t\tfor (const [property, value] of Object.entries(resolvedSize ?? {})) {\n\t\t\t\t\t\tif (property.startsWith(\"--\")) {\n\t\t\t\t\t\t\tconst customProperty = property as `--${string}`;\n\t\t\t\t\t\t\tif (value !== undefined) {\n\t\t\t\t\t\t\t\tfloatingStyle.setProperty(customProperty, value);\n\t\t\t\t\t\t\t\tnextCustomProperties.add(customProperty);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (value !== undefined) {\n\t\t\t\t\t\t\tconst standardProperty = property as FloatingListboxStandardSizeProperty;\n\t\t\t\t\t\t\tnextStandardProperties.set(standardProperty, floatingStyle[standardProperty]);\n\t\t\t\t\t\t\tfloatingStyle[standardProperty] = value;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfloatingListboxAppliedSizeState.set(elements.floating, {\n\t\t\t\t\t\tcustomProperties: nextCustomProperties,\n\t\t\t\t\t\tstandardProperties: nextStandardProperties\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t})\n\t\t],\n\t\twhileElementsMounted: autoUpdate\n\t});\n\n\tconst dismiss = useDismiss(context);\n\tconst role = useRole(context, { role: \"listbox\" });\n\tconst { getFloatingProps } = useInteractions([dismiss, role]);\n\n\tconst canOpen = allowOpenWithoutOptions || options.length > 0;\n\n\tuseEffect(() => {\n\t\tif (!resolvedOpen || activeIndex < 0) {\n\t\t\treturn;\n\t\t}\n\n\t\toptionRefs.current[activeIndex]?.scrollIntoView({ block: \"nearest\" });\n\t}, [activeIndex, resolvedOpen]);\n\n\tuseEffect(() => {\n\t\tif (!resolvedOpen || !focusFloatingOnOpen) {\n\t\t\treturn;\n\t\t}\n\n\t\trequestAnimationFrame(() => {\n\t\t\trefs.floating.current?.focus();\n\t\t});\n\t}, [focusFloatingOnOpen, resolvedOpen, refs]);\n\n\tconst close = () => {\n\t\tsetResolvedOpen(false);\n\n\t\tif (restoreFocusOnClose) {\n\t\t\tconst referenceElement = refs.domReference.current;\n\t\t\tif (referenceElement instanceof HTMLElement) {\n\t\t\t\treferenceElement.focus();\n\t\t\t}\n\t\t}\n\t};\n\n\tconst openWithIndex = (nextIndex: number) => {\n\t\tif (disabled || !canOpen) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (options.length > 0) {\n\t\t\tsetActiveIndex(resolveActiveIndex(nextIndex));\n\t\t}\n\n\t\tsetResolvedOpen(true);\n\t};\n\n\tconst openList = () => {\n\t\topenWithIndex(selectedIndex >= 0 ? selectedIndex : findFirstEnabledIndex(options, getOptionDisabled));\n\t};\n\n\tconst toggleOpen = () => {\n\t\tif (resolvedOpen) {\n\t\t\tclose();\n\t\t\treturn;\n\t\t}\n\n\t\topenList();\n\t};\n\n\tconst selectOption = (option: T) => {\n\t\tif (getOptionDisabled?.(option)) {\n\t\t\treturn;\n\t\t}\n\n\t\tonSelect?.(option);\n\n\t\tif (closeOnSelect) {\n\t\t\tclose();\n\t\t}\n\t};\n\n\tconst selectActiveOption = () => {\n\t\tif (activeIndex < 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst activeOption = options[activeIndex];\n\t\tif (activeOption !== undefined) {\n\t\t\tselectOption(activeOption);\n\t\t}\n\t};\n\n\tconst moveActiveToFirst = () => {\n\t\tsetActiveIndex(findFirstEnabledIndex(options, getOptionDisabled));\n\t};\n\n\tconst moveActiveToLast = () => {\n\t\tsetActiveIndex(findLastEnabledIndex(options, getOptionDisabled));\n\t};\n\n\tconst handleReferenceKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {\n\t\tif (disabled) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === \"ArrowDown\") {\n\t\t\tevent.preventDefault();\n\n\t\t\tif (!resolvedOpen) {\n\t\t\t\topenWithIndex(selectedIndex >= 0 ? selectedIndex : findFirstEnabledIndex(options, getOptionDisabled));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tsetActiveIndex((currentIndex) => findNextEnabledIndex(options, resolveActiveIndex(currentIndex), 1, getOptionDisabled));\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === \"ArrowUp\") {\n\t\t\tevent.preventDefault();\n\n\t\t\tif (!resolvedOpen) {\n\t\t\t\topenWithIndex(selectedIndex >= 0 ? selectedIndex : findLastEnabledIndex(options, getOptionDisabled));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tsetActiveIndex((currentIndex) => findNextEnabledIndex(options, resolveActiveIndex(currentIndex), -1, getOptionDisabled));\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === \"Home\" && resolvedOpen) {\n\t\t\tevent.preventDefault();\n\t\t\tmoveActiveToFirst();\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === \"End\" && resolvedOpen) {\n\t\t\tevent.preventDefault();\n\t\t\tmoveActiveToLast();\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === \"Escape\" && resolvedOpen) {\n\t\t\tevent.preventDefault();\n\t\t\tclose();\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === \"Tab\" && resolvedOpen) {\n\t\t\tclose();\n\t\t}\n\t};\n\n\tconst handleFloatingKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {\n\t\tif (event.target !== event.currentTarget) {\n\t\t\tif (event.key === \"Escape\") {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tclose();\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === \"ArrowDown\") {\n\t\t\tevent.preventDefault();\n\t\t\tsetActiveIndex((currentIndex) => findNextEnabledIndex(options, resolveActiveIndex(currentIndex), 1, getOptionDisabled));\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === \"ArrowUp\") {\n\t\t\tevent.preventDefault();\n\t\t\tsetActiveIndex((currentIndex) => findNextEnabledIndex(options, resolveActiveIndex(currentIndex), -1, getOptionDisabled));\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === \"Home\") {\n\t\t\tevent.preventDefault();\n\t\t\tmoveActiveToFirst();\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === \"End\") {\n\t\t\tevent.preventDefault();\n\t\t\tmoveActiveToLast();\n\t\t\treturn;\n\t\t}\n\n\t\tif (handleKeyboardActivation(event, selectActiveOption)) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === \"Escape\") {\n\t\t\tevent.preventDefault();\n\t\t\tclose();\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === \"Tab\") {\n\t\t\tclose();\n\t\t}\n\t};\n\n\tconst getOptionId = (listId: string, index: number) => `${listId}-option-${index}`;\n\tconst getActiveOptionId = (listId: string) => (activeIndex >= 0 ? getOptionId(listId, activeIndex) : undefined);\n\n\treturn {\n\t\topen: resolvedOpen,\n\t\tsetOpen: setResolvedOpen,\n\t\tactiveIndex,\n\t\trefs,\n\t\tcontext,\n\t\tfloatingStyles,\n\t\tgetFloatingProps,\n\t\tsetReference: refs.setReference,\n\t\tsetFloating: refs.setFloating,\n\t\tsetOptionRef: (index: number, node: HTMLElement | null) => {\n\t\t\toptionRefs.current[index] = node;\n\t\t},\n\t\tclose,\n\t\topenList,\n\t\topenWithIndex,\n\t\ttoggleOpen,\n\t\tselectOption,\n\t\tselectActiveOption,\n\t\thandleReferenceKeyDown,\n\t\thandleFloatingKeyDown,\n\t\tgetOptionId,\n\t\tgetActiveOptionId\n\t};\n}\n","import { type ComponentType, useMemo } from \"react\";\n\nimport { createLazyComponent } from \"../utils/createLazyComponent\";\n\n/**\n * Хук для создания ленивой компоненты, которая будет загружена только при первом рендере.\n *\n * @example\n *\n * ```tsx\n * const MyComponent = () => {\n * const TextEditor = useLazyComponent(\n * () => import(\"@components/content/article/editor/TextEditor\"),\n * \"TextEditor\"\n * );\n *\n * return (\n * <Suspense fallback={<div>Загрузка...</div>}>\n * <TextEditor />\n * </Suspense>\n * );\n * };\n * ```\n *\n * @param importFn - функция, которая возвращает промис, который резолвится в объект с компонентами\n * @param componentName - имя компонента, который нужно загрузить\n * @returns\n */\nconst useLazyComponent = <TProps extends object>(\n\timportFn: () => Promise<Record<string, ComponentType<TProps>> | null>,\n\tcomponentName: string,\n\tcacheKey?: string\n) => {\n\treturn useMemo(() => createLazyComponent(importFn, componentName, cacheKey), [cacheKey, importFn, componentName]);\n};\n\nexport { useLazyComponent };\n","import { useEffect, useRef } from \"react\";\n\nexport function useReferenceTrace<T>(name: string, value: T) {\n\tconst previousRef = useRef(value);\n\n\tuseEffect(() => {\n\t\tif (!__DEV__) return;\n\n\t\tconst referenceChanged = !Object.is(previousRef.current, value);\n\n\t\tconsole.debug(`[render trace] ${name}`, { referenceChanged, previous: previousRef.current, current: value });\n\n\t\tpreviousRef.current = value;\n\t});\n}\n","import { useLocation } from \"@tanstack/react-router\";\n\nimport { SearchParams } from \"./types\";\n\nexport function useSearchLink<K extends PropertyKey>() {\n\tconst location = useLocation();\n\n\tconst buildLink = (params: SearchParams<K>) => {\n\t\tconst next = new URLSearchParams(location.searchStr);\n\n\t\tparams = params || [];\n\n\t\tfor (const param in params) {\n\t\t\tconst value = params[param];\n\n\t\t\tif (value) {\n\t\t\t\tnext.set(param, String(value));\n\t\t\t} else {\n\t\t\t\tnext.delete(param);\n\t\t\t}\n\t\t}\n\n\t\tconst searchString = next.toString();\n\t\treturn `${location.pathname}${searchString ? `?${searchString}` : \"\"}${location.hash}`;\n\t};\n\n\treturn { buildLink };\n}\n","import { useMemo } from \"react\";\n\nimport { AnyRouter, useNavigate, useSearch } from \"@tanstack/react-router\";\n\nimport { SearchParams, SearchParamsUpdater } from \"./types\";\n\nexport function useSearchParams<K extends PropertyKey>(allowedKeys?: (keyof K)[]) {\n\tconst params = useSearch<AnyRouter, string, false, true, SearchParams<K>>({ strict: false });\n\tconst navigate = useNavigate();\n\n\t// Фильтруем параметры, оставляя только разрешенные ключи\n\tconst filteredParams = useMemo(() => {\n\t\tif (!allowedKeys) return params;\n\n\t\treturn Object.fromEntries(Object.entries(params).filter(([key]) => allowedKeys.includes(key as keyof K))) as SearchParams<K>;\n\t}, [params, allowedKeys]);\n\n\tconst setParams = (next: SearchParamsUpdater<K>) =>\n\t\tnavigate({\n\t\t\tto: \".\",\n\t\t\tsearch: (prev: SearchParams<K>) => ({\n\t\t\t\t...prev,\n\t\t\t\t...next\n\t\t\t}),\n\t\t\treplace: true\n\t\t});\n\n\treturn { params, setParams, filteredParams };\n}\n","import { useCallback, useEffect, useRef } from \"react\";\n\nexport type ThrottledCallback<T> = {\n\t/**\n\t * Основной вход: вызывай на каждом частом событии (например, onNodesChange).\n\t * Колбэк `cb` будет вызываться не чаще, чем 1 раз в `intervalMs`.\n\t *\n\t * Стратегия: last-write-wins — если за интервал пришло много значений,\n\t * по окончании интервала будет применено последнее.\n\t */\n\tcall: (arg: T) => void;\n\n\t/**\n\t * Немедленно применить последнее накопленное значение (если оно есть),\n\t * отменив запланированный вызов (если он был).\n\t * Полезно на:\n\t * - pointerup/mouseup (конец перетаскивания)\n\t * - blur/visibilitychange/beforeunload\n\t * - unmount (если хочешь гарантированно сохранить финальное состояние)\n\t */\n\tflush: () => void;\n\n\t/**\n\t * Отменить запланированный вызов и забыть накопленное значение.\n\t * Полезно, если ты переключаешь редактор/граф и не хочешь “долетов” старых данных.\n\t */\n\tcancel: () => void;\n};\n\n/**\n * useThrottledCallback (React 19 safe)\n *\n * Проблема:\n * - В xyflow onNodesChange/onEdgesChange может сыпаться очень часто.\n * - Если каждый раз писать “сериализуемый снапшот” в zustand → лишние ререндеры/CPU/GC.\n *\n * Решение:\n * - throttle: вызывать cb не чаще, чем 1 раз в intervalMs.\n * - last-write-wins: если за интервал пришло 50 обновлений — применим последнее.\n *\n * React 19 нюанс:\n * - нельзя мутировать ref.current во время render (линтер/правило).\n * - поэтому cbRef обновляем в useEffect.\n */\nexport function useThrottledCallback<T>(\n\tcb: (arg: T) => void,\n\tintervalMs: number,\n\toptions?: { flushOnUnmount?: boolean }\n): ThrottledCallback<T> {\n\t/**\n\t * Актуальная версия cb (обновляется только после commit’а).\n\t * Это решает \"stale closure\": call/flush/cancel стабильны,\n\t * но всегда дергают самый свежий cb.\n\t */\n\tconst cbRef = useRef<(arg: T) => void>(cb);\n\n\tuseEffect(() => {\n\t\tcbRef.current = cb;\n\t}, [cb]);\n\n\t/**\n\t * Таймер отложенного выполнения.\n\t * Он нужен, когда call() пришёл раньше, чем завершился текущий throttle-интервал:\n\t * мы планируем “выполнить в конце окна”.\n\t */\n\tconst tRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n\t/**\n\t * Накопленное “последнее значение”.\n\t * Всегда хранится последний arg, пришедший через call().\n\t */\n\tconst lastRef = useRef<T | null>(null);\n\n\t/**\n\t * Время (timestamp) последнего РЕАЛЬНОГО вызова cb.\n\t * Нужно, чтобы вычислять: можно ли выполнить сейчас или нужно подождать.\n\t */\n\tconst lastInvokeTsRef = useRef<number>(0);\n\n\t/**\n\t * cancel():\n\t * - отменяем отложенный таймер,\n\t * - забываем накопленное значение.\n\t * Важно: это не вызывает cb.\n\t */\n\tconst cancel = useCallback(() => {\n\t\tif (tRef.current) {\n\t\t\tclearTimeout(tRef.current);\n\t\t\ttRef.current = null;\n\t\t}\n\t\tlastRef.current = null;\n\t}, []);\n\n\t/**\n\t * flush():\n\t * - если есть накопленное значение, вызываем cb немедленно,\n\t * - чистим таймер,\n\t * - фиксируем lastInvokeTsRef (чтобы окно throttle считалось от flush тоже).\n\t */\n\tconst flush = useCallback(() => {\n\t\tconst v = lastRef.current;\n\t\tif (v === null) return;\n\n\t\t// Сбрасываем накопленное ДО вызова cb на случай,\n\t\t// если cb внутри синхронно вызовет call() снова.\n\t\tlastRef.current = null;\n\n\t\tif (tRef.current) {\n\t\t\tclearTimeout(tRef.current);\n\t\t\ttRef.current = null;\n\t\t}\n\n\t\tlastInvokeTsRef.current = Date.now();\n\t\tcbRef.current(v);\n\t}, []);\n\n\t/**\n\t * call(arg):\n\t * 1) сохраняем arg как последнее значение (last-write-wins)\n\t * 2) если с момента последнего вызова прошло достаточно времени → вызываем сразу\n\t * 3) иначе — планируем flush ровно в момент, когда throttle-окно закончится\n\t *\n\t * При этом мы гарантируем:\n\t * - не более одного cb вызова за intervalMs\n\t * - и что “последнее значение за интервал” будет применено в конце.\n\t */\n\tconst call = useCallback(\n\t\t(arg: T) => {\n\t\t\tlastRef.current = arg;\n\n\t\t\tconst now = Date.now();\n\t\t\tconst elapsed = now - lastInvokeTsRef.current;\n\n\t\t\t// Если окно уже прошло — можно выполнить немедленно.\n\t\t\tif (elapsed >= intervalMs) {\n\t\t\t\t// Немедленный вызов = “leading edge”.\n\t\t\t\t// Берём последнее значение, очищаем lastRef и вызываем cb.\n\t\t\t\tconst v = lastRef.current;\n\t\t\t\tlastRef.current = null;\n\n\t\t\t\t// На всякий случай прибираем таймер, если он был.\n\t\t\t\tif (tRef.current) {\n\t\t\t\t\tclearTimeout(tRef.current);\n\t\t\t\t\ttRef.current = null;\n\t\t\t\t}\n\n\t\t\t\tlastInvokeTsRef.current = now;\n\t\t\t\tif (v !== null) cbRef.current(v);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Иначе — “trailing edge”: планируем выполнение в конце окна.\n\t\t\t// Важно: если таймер уже запланирован — НЕ пересоздаём,\n\t\t\t// потому что trailing-вызов должен случиться в конкретный момент (конец окна),\n\t\t\t// а значение мы и так обновляем через lastRef.current.\n\t\t\tif (!tRef.current) {\n\t\t\t\tconst wait = intervalMs - elapsed;\n\n\t\t\t\ttRef.current = setTimeout(() => {\n\t\t\t\t\ttRef.current = null;\n\t\t\t\t\t// В конце окна применяем последнее накопленное значение\n\t\t\t\t\tflush();\n\t\t\t\t}, wait);\n\t\t\t}\n\t\t},\n\t\t[intervalMs, flush]\n\t);\n\n\t/**\n\t * Cleanup на unmount:\n\t * - по умолчанию cancel (не делаем побочных эффектов при размонтировании)\n\t * - при flushOnUnmount=true — пытаемся сохранить последнее значение.\n\t *\n\t * Для твоего кейса (сериализация финального snapshot) чаще полезно flushOnUnmount=true.\n\t */\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tif (options?.flushOnUnmount) flush();\n\t\t\telse cancel();\n\t\t};\n\t}, [cancel, flush, options?.flushOnUnmount]);\n\n\treturn { call, flush, cancel };\n}\n"],"mappings":"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"}
@@ -1,4 +1,26 @@
1
1
  import { Placement } from "@floating-ui/react";
2
+ /** Геометрия, которую Floating UI вычислил для доступной стороны listbox. */
3
+ export type FloatingListboxSizeContext = {
4
+ availableWidth: number;
5
+ availableHeight: number;
6
+ viewportWidth: number;
7
+ viewportHeight: number;
8
+ referenceWidth: number;
9
+ referenceHeight: number;
10
+ };
11
+ /**
12
+ * Размеры floating-элемента и CSS custom properties, которые специализированный
13
+ * picker может вычислить из доступного viewport без собственного middleware.
14
+ */
15
+ export type FloatingListboxSizeStyle = {
16
+ width?: string;
17
+ minWidth?: string;
18
+ maxWidth?: string;
19
+ maxHeight?: string;
20
+ } & {
21
+ [customProperty: `--${string}`]: string | undefined;
22
+ };
23
+ export type FloatingListboxSizeResolver = (context: FloatingListboxSizeContext) => FloatingListboxSizeStyle;
2
24
  interface UseFloatingListboxParams<T> {
3
25
  options: readonly T[];
4
26
  selectedIndex: number;
@@ -12,8 +34,9 @@ interface UseFloatingListboxParams<T> {
12
34
  focusFloatingOnOpen?: boolean;
13
35
  allowOpenWithoutOptions?: boolean;
14
36
  restoreFocusOnClose?: boolean;
37
+ resolveFloatingSize?: FloatingListboxSizeResolver;
15
38
  }
16
- export declare function useFloatingListbox<T>({ options, selectedIndex, open, onOpenChange, getOptionDisabled, onSelect, disabled, placement, closeOnSelect, focusFloatingOnOpen, allowOpenWithoutOptions, restoreFocusOnClose }: UseFloatingListboxParams<T>): {
39
+ export declare function useFloatingListbox<T>({ options, selectedIndex, open, onOpenChange, getOptionDisabled, onSelect, disabled, placement, closeOnSelect, focusFloatingOnOpen, allowOpenWithoutOptions, restoreFocusOnClose, resolveFloatingSize }: UseFloatingListboxParams<T>): {
17
40
  open: boolean;
18
41
  setOpen: (open: boolean) => void;
19
42
  activeIndex: number;
@@ -1 +1 @@
1
- {"version":3,"file":"useFloatingListbox.d.ts","sourceRoot":"","sources":["../../src/hooks/useFloatingListbox.ts"],"names":[],"mappings":"AAEA,OAAO,EAKN,SAAS,EAMT,MAAM,oBAAoB,CAAC;AAI5B,UAAU,wBAAwB,CAAC,CAAC;IACnC,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC;IAC3C,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,EACrC,OAAO,EACP,aAAa,EACb,IAAI,EACJ,YAAY,EACZ,iBAAiB,EACjB,QAAQ,EACR,QAAQ,EACR,SAA0B,EAC1B,aAAoB,EACpB,mBAA0B,EAC1B,uBAA+B,EAC/B,mBAA0B,EAC1B,EAAE,wBAAwB,CAAC,CAAC,CAAC;;oBAxBP,OAAO,KAAK,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAkRf,MAAM,QAAQ,WAAW,GAAG,IAAI;;;+BAjKrB,MAAM;;2BAyBV,CAAC;;oCA+BQ,KAAK,CAAC,aAAa,CAAC,WAAW,CAAC;mCAoDjC,KAAK,CAAC,aAAa,CAAC,WAAW,CAAC;0BAwCzC,MAAM,SAAS,MAAM;gCACf,MAAM;EA0BzC"}
1
+ {"version":3,"file":"useFloatingListbox.d.ts","sourceRoot":"","sources":["../../src/hooks/useFloatingListbox.ts"],"names":[],"mappings":"AAEA,OAAO,EAKN,SAAS,EAMT,MAAM,oBAAoB,CAAC;AAI5B,6EAA6E;AAC7E,MAAM,MAAM,0BAA0B,GAAG;IACxC,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG;IACH,CAAC,cAAc,EAAE,KAAK,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC;CACpD,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG,CAAC,OAAO,EAAE,0BAA0B,KAAK,wBAAwB,CAAC;AAY5G,UAAU,wBAAwB,CAAC,CAAC;IACnC,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC;IAC3C,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,mBAAmB,CAAC,EAAE,2BAA2B,CAAC;CAClD;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,EACrC,OAAO,EACP,aAAa,EACb,IAAI,EACJ,YAAY,EACZ,iBAAiB,EACjB,QAAQ,EACR,QAAQ,EACR,SAA0B,EAC1B,aAAoB,EACpB,mBAA0B,EAC1B,uBAA+B,EAC/B,mBAA0B,EAC1B,mBAAmB,EACnB,EAAE,wBAAwB,CAAC,CAAC,CAAC;;oBA1BP,OAAO,KAAK,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BA0Uf,MAAM,QAAQ,WAAW,GAAG,IAAI;;;+BA1KrB,MAAM;;2BAyBV,CAAC;;oCA+BQ,KAAK,CAAC,aAAa,CAAC,WAAW,CAAC;mCAoDjC,KAAK,CAAC,aAAa,CAAC,WAAW,CAAC;0BAiDzC,MAAM,SAAS,MAAM;gCACf,MAAM;EA0BzC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryuzaki13/react-foundation-lib",
3
- "version": "1.1.8",
3
+ "version": "1.1.10",
4
4
  "description": "Reusable React and TypeScript foundation utilities, hooks, and primitives.",
5
5
  "license": "MIT",
6
6
  "author": "Ryuzaki13",