fui-material 2.8.3 → 2.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/f-ui-kit.es.js +489 -134
- package/dist/f-ui-kit.es.js.map +1 -1
- package/dist/f-ui-kit.umd.js +2 -2
- package/dist/f-ui-kit.umd.js.map +1 -1
- package/dist/index.css +1 -1
- package/dist/types/material/TableComponents/FTable/FTable.d.ts +2 -0
- package/dist/types/material/TableComponents/FTableActions.d.ts +6 -0
- package/dist/types/material/TableComponents/FTableBody/FTableBody.d.ts +4 -27
- package/dist/types/material/TableComponents/FTableDataCell/FTableDataCell.d.ts +8 -0
- package/dist/types/material/TableComponents/FTableHeaderCell/FTableHeaderCell.d.ts +2 -0
- package/dist/types/material/TableComponents/FTableTextTooltip.d.ts +7 -0
- package/dist/types/material/TableComponents/TableControlsContext.d.ts +6 -0
- package/dist/types/material/TableComponents/TruncateContext.d.ts +1 -0
- package/dist/types/material/TableComponents/tableRows.d.ts +24 -0
- package/dist/types/material/TableComponents/virtualRows.d.ts +4 -0
- package/dist/types/material/index.d.ts +1 -0
- package/package.json +1 -1
package/dist/f-ui-kit.es.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
|
-
import React, { forwardRef, useRef, useState, useEffect,
|
|
2
|
+
import React, { forwardRef, useRef, useState, useEffect, createContext, isValidElement, Children, Fragment as Fragment$1, cloneElement, useContext, useMemo, useLayoutEffect, useDeferredValue, useCallback, useId } from "react";
|
|
3
3
|
import ReactDOM, { createPortal } from "react-dom";
|
|
4
4
|
import JSZip from "jszip";
|
|
5
5
|
import initializeModule from "allorion-exporting-html-to-docx";
|
|
@@ -712,26 +712,39 @@ const FPaper = ({
|
|
|
712
712
|
}
|
|
713
713
|
);
|
|
714
714
|
};
|
|
715
|
-
const
|
|
716
|
-
const left$1 = "
|
|
717
|
-
const center = "
|
|
718
|
-
const right = "
|
|
715
|
+
const TableControlsContext = createContext({ query: "", sort: null });
|
|
716
|
+
const left$1 = "_left_blx02_68";
|
|
717
|
+
const center = "_center_blx02_71";
|
|
718
|
+
const right = "_right_blx02_74";
|
|
719
|
+
const justify = "_justify_blx02_77";
|
|
720
|
+
const more = "_more_blx02_141";
|
|
719
721
|
const styles$t = {
|
|
720
|
-
"f-table-component": "_f-table-
|
|
721
|
-
"f-table-component__table": "_f-table-
|
|
722
|
-
|
|
723
|
-
"
|
|
724
|
-
"f-table-component__table_header": "_f-table-
|
|
725
|
-
"
|
|
726
|
-
"f-table-
|
|
722
|
+
"f-table-component": "_f-table-component_blx02_1",
|
|
723
|
+
"f-table-component__table": "_f-table-component__table_blx02_12",
|
|
724
|
+
"f-table-component__table_header": "_f-table-component__table_header_blx02_21",
|
|
725
|
+
"is-sticky": "_is-sticky_blx02_25",
|
|
726
|
+
"f-table-component__table_header-cell": "_f-table-component__table_header-cell_blx02_36",
|
|
727
|
+
"f-table-component__table_body-cell": "_f-table-component__table_body-cell_blx02_36",
|
|
728
|
+
"f-table-component__table_body": "_f-table-component__table_body_blx02_36",
|
|
729
|
+
"f-table-component__table_row": "_f-table-component__table_row_blx02_53",
|
|
730
|
+
"f-table-component__table_row_no-hover": "_f-table-component__table_row_no-hover_blx02_53",
|
|
731
|
+
"f-table-component__table_footer": "_f-table-component__table_footer_blx02_59",
|
|
727
732
|
left: left$1,
|
|
728
733
|
center,
|
|
729
734
|
right,
|
|
730
|
-
|
|
731
|
-
"
|
|
732
|
-
"
|
|
733
|
-
"
|
|
734
|
-
|
|
735
|
+
justify,
|
|
736
|
+
"cell-text": "_cell-text_blx02_81",
|
|
737
|
+
"cell-preview": "_cell-preview_blx02_105",
|
|
738
|
+
"cell-reveal": "_cell-reveal_blx02_116",
|
|
739
|
+
more,
|
|
740
|
+
"row-menu": "_row-menu_blx02_170",
|
|
741
|
+
"table-tools": "_table-tools_blx02_257",
|
|
742
|
+
"table-search": "_table-search_blx02_263",
|
|
743
|
+
"table-sort": "_table-sort_blx02_292",
|
|
744
|
+
"table-empty": "_table-empty_blx02_319",
|
|
745
|
+
"cell-tooltip-anchor": "_cell-tooltip-anchor_blx02_325",
|
|
746
|
+
"cell-tooltip": "_cell-tooltip_blx02_325",
|
|
747
|
+
"cell-tooltip-in": "_cell-tooltip-in_blx02_1"
|
|
735
748
|
};
|
|
736
749
|
const FTableHead = ({
|
|
737
750
|
st,
|
|
@@ -752,99 +765,208 @@ const FTableHead = ({
|
|
|
752
765
|
}
|
|
753
766
|
);
|
|
754
767
|
};
|
|
768
|
+
const TruncateContext = createContext(void 0);
|
|
769
|
+
function tableChildren(children, prefix = "") {
|
|
770
|
+
return Children.toArray(children).flatMap((child) => {
|
|
771
|
+
if (!isValidElement(child)) return [child];
|
|
772
|
+
const key = `${prefix}${child.key}`;
|
|
773
|
+
return child.type === Fragment$1 ? tableChildren(child.props.children, `${key}/`) : [cloneElement(child, { key })];
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
function cellText(children) {
|
|
777
|
+
return Children.toArray(children).map((child) => {
|
|
778
|
+
if (typeof child === "string" || typeof child === "number") return String(child);
|
|
779
|
+
if (!isValidElement(child) || child.props.hidden || child.props["aria-hidden"] === true || child.props["aria-hidden"] === "true") return "";
|
|
780
|
+
if (typeof child.type !== "string" && child.type !== Fragment$1) return "";
|
|
781
|
+
if (["button", "input", "select", "textarea", "svg"].includes(String(child.type))) return "";
|
|
782
|
+
return cellText(child.props.children);
|
|
783
|
+
}).join("");
|
|
784
|
+
}
|
|
785
|
+
const normalize = (text) => text.normalize("NFKC").toLocaleLowerCase("ru").replace(/\s+/g, " ").trim();
|
|
786
|
+
const collator = new Intl.Collator("ru", { numeric: true, sensitivity: "base" });
|
|
787
|
+
function indexTableRows(children) {
|
|
788
|
+
const rows = tableChildren(children);
|
|
789
|
+
const sections = [{ heading: [], groups: [] }];
|
|
790
|
+
let section = sections[0];
|
|
791
|
+
let group;
|
|
792
|
+
let spanEnd = 0;
|
|
793
|
+
let columnCount = 1;
|
|
794
|
+
let hasRowSpans = false;
|
|
795
|
+
rows.forEach((row, index) => {
|
|
796
|
+
const cells = isValidElement(row) ? tableChildren(row.props.children).filter(isValidElement) : [];
|
|
797
|
+
columnCount = Math.max(columnCount, cells.reduce((sum, cell) => sum + Math.max(1, cell.props.colSpan ?? cell.props.col ?? 1), 0));
|
|
798
|
+
const heading = index >= spanEnd && cells.length > 0 && cells.every((cell) => !cell.props.sortKey && (cell.props.colSpan ?? cell.props.col ?? 1) > 1);
|
|
799
|
+
if (heading) {
|
|
800
|
+
section = { heading: [row], groups: [] };
|
|
801
|
+
sections.push(section);
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
if (!group || index >= spanEnd) {
|
|
805
|
+
group = { rows: [], text: "", values: /* @__PURE__ */ new Map() };
|
|
806
|
+
section.groups.push(group);
|
|
807
|
+
}
|
|
808
|
+
group.rows.push(row);
|
|
809
|
+
cells.forEach((cell) => {
|
|
810
|
+
const props = cell.props;
|
|
811
|
+
const span = props.rowSpan ?? props.row ?? 1;
|
|
812
|
+
if (span === 0 || span > 1) {
|
|
813
|
+
hasRowSpans = true;
|
|
814
|
+
spanEnd = Math.max(spanEnd, span === 0 ? rows.length : index + span);
|
|
815
|
+
}
|
|
816
|
+
const text = props.searchValue ?? cellText(props.children);
|
|
817
|
+
group.text += ` ${normalize(text)}`;
|
|
818
|
+
if (props.sortKey && !group.values.has(props.sortKey)) {
|
|
819
|
+
group.values.set(props.sortKey, props.sortValue ?? text);
|
|
820
|
+
}
|
|
821
|
+
});
|
|
822
|
+
});
|
|
823
|
+
return { rows, sections, columnCount, hasRowSpans };
|
|
824
|
+
}
|
|
825
|
+
function selectTableRows(index, query, sort) {
|
|
826
|
+
const needle = normalize(query);
|
|
827
|
+
if (!needle && !sort) return index.rows;
|
|
828
|
+
return index.sections.flatMap((section) => {
|
|
829
|
+
const groups = section.groups.filter((group) => !needle || group.text.includes(needle));
|
|
830
|
+
if (sort) groups.sort((a, b) => {
|
|
831
|
+
const left2 = a.values.get(sort.key);
|
|
832
|
+
const right2 = b.values.get(sort.key);
|
|
833
|
+
const empty = (value) => value === void 0 || String(value).trim() === "" || typeof value === "number" && !Number.isFinite(value);
|
|
834
|
+
if (empty(left2) || empty(right2)) return Number(empty(left2)) - Number(empty(right2));
|
|
835
|
+
const comparison = typeof left2 === "number" && typeof right2 === "number" ? left2 - right2 : collator.compare(String(left2), String(right2));
|
|
836
|
+
return sort.direction === "ascending" ? comparison : -comparison;
|
|
837
|
+
});
|
|
838
|
+
return groups.length ? [...section.heading, ...groups.flatMap((group) => group.rows)] : needle ? [] : section.heading;
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
function rowAt(offsets, position) {
|
|
842
|
+
let low = 0;
|
|
843
|
+
let high = offsets.length - 1;
|
|
844
|
+
while (low < high) {
|
|
845
|
+
const middle = Math.floor((low + high) / 2);
|
|
846
|
+
if (offsets[middle + 1] <= position) low = middle + 1;
|
|
847
|
+
else high = middle;
|
|
848
|
+
}
|
|
849
|
+
return Math.min(low, Math.max(0, offsets.length - 2));
|
|
850
|
+
}
|
|
851
|
+
function virtualRowRange(offsets, top, viewportHeight, minimumRows) {
|
|
852
|
+
const count = Math.max(0, offsets.length - 1);
|
|
853
|
+
if (!count) return { start: 0, end: 0 };
|
|
854
|
+
const first = rowAt(offsets, Math.max(0, top));
|
|
855
|
+
const last = rowAt(offsets, Math.max(0, top) + Math.max(0, viewportHeight));
|
|
856
|
+
const start = Math.max(0, Math.min(first - 3, count - Math.floor(minimumRows)));
|
|
857
|
+
const end = Math.min(count, Math.max(last + 4, start + Math.floor(minimumRows)));
|
|
858
|
+
return { start, end };
|
|
859
|
+
}
|
|
755
860
|
const FTableBody = ({
|
|
756
861
|
st,
|
|
757
862
|
children,
|
|
758
863
|
textAlignment = "left",
|
|
759
|
-
// По умолчанию выравнивание слева
|
|
760
864
|
tableWrapperRef,
|
|
761
|
-
visibleRowCount
|
|
865
|
+
visibleRowCount,
|
|
866
|
+
truncateAt,
|
|
762
867
|
...props
|
|
763
868
|
}) => {
|
|
764
869
|
const tableBodyRef = useRef(null);
|
|
765
|
-
const
|
|
766
|
-
const
|
|
767
|
-
const
|
|
768
|
-
const [
|
|
769
|
-
const
|
|
870
|
+
const { query, sort } = useContext(TableControlsContext);
|
|
871
|
+
const index = useMemo(() => indexTableRows(children), [children]);
|
|
872
|
+
const rows = useMemo(() => selectTableRows(index, query, sort), [index, query, sort]);
|
|
873
|
+
const [viewport, setViewport] = useState({ top: 0, height: 0 });
|
|
874
|
+
const [heights, setHeights] = useState(() => /* @__PURE__ */ new Map());
|
|
875
|
+
const virtual = Boolean(tableWrapperRef) && visibleRowCount !== void 0 && Number.isFinite(visibleRowCount) && visibleRowCount >= 1 && !index.hasRowSpans;
|
|
876
|
+
const keyOf = (row, index2) => React.isValidElement(row) ? String(row.key ?? index2) : String(index2);
|
|
877
|
+
const offsets = useMemo(() => {
|
|
878
|
+
const result = [0];
|
|
879
|
+
if (virtual) rows.forEach((row, index2) => result.push(result[index2] + (heights.get(keyOf(row, index2)) ?? 32)));
|
|
880
|
+
return result;
|
|
881
|
+
}, [rows, heights, virtual]);
|
|
882
|
+
const { start, end } = virtual ? virtualRowRange(offsets, viewport.top, viewport.height, visibleRowCount) : { start: 0, end: rows.length };
|
|
883
|
+
const visibleRows = useMemo(() => rows.slice(start, end), [rows, start, end]);
|
|
884
|
+
useLayoutEffect(() => {
|
|
885
|
+
setViewport((previous) => ({ ...previous, top: 0 }));
|
|
886
|
+
if (tableWrapperRef == null ? void 0 : tableWrapperRef.current) tableWrapperRef.current.scrollTop = 0;
|
|
887
|
+
}, [query, sort, tableWrapperRef]);
|
|
770
888
|
useEffect(() => {
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
if (!(tableWrapperRef == null ? void 0 : tableWrapperRef.current) || !tableBodyRef.current) return;
|
|
778
|
-
const wrapper = tableWrapperRef.current;
|
|
779
|
-
const scrollTop = wrapper.scrollTop;
|
|
780
|
-
let acc = 0;
|
|
781
|
-
let idx = 0;
|
|
782
|
-
for (let i = 0; i < rowHeights.length; i++) {
|
|
783
|
-
if (acc + rowHeights[i] > scrollTop) {
|
|
784
|
-
idx = i;
|
|
785
|
-
break;
|
|
786
|
-
}
|
|
787
|
-
acc += rowHeights[i];
|
|
788
|
-
}
|
|
789
|
-
setStartIdx(idx);
|
|
790
|
-
}, [rowHeights, tableWrapperRef, virtualizationEnabled]);
|
|
791
|
-
const handleScroll = useCallback(() => {
|
|
792
|
-
if (!virtualizationEnabled) return;
|
|
793
|
-
requestAnimationFrame(updateStartIdx);
|
|
794
|
-
}, [updateStartIdx, virtualizationEnabled]);
|
|
889
|
+
const keys = new Set(index.rows.map(keyOf));
|
|
890
|
+
setHeights((previous) => {
|
|
891
|
+
const next = new Map([...previous].filter(([key]) => keys.has(key)));
|
|
892
|
+
return next.size === previous.size ? previous : next;
|
|
893
|
+
});
|
|
894
|
+
}, [index]);
|
|
795
895
|
useEffect(() => {
|
|
796
|
-
|
|
797
|
-
if (!
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
896
|
+
const wrapper = tableWrapperRef == null ? void 0 : tableWrapperRef.current;
|
|
897
|
+
if (!virtual || !wrapper) return;
|
|
898
|
+
let frame = 0;
|
|
899
|
+
let width = wrapper.clientWidth;
|
|
900
|
+
const measureScroll = () => {
|
|
901
|
+
cancelAnimationFrame(frame);
|
|
902
|
+
frame = requestAnimationFrame(() => {
|
|
903
|
+
const body = tableBodyRef.current;
|
|
904
|
+
if (body) {
|
|
905
|
+
const top = Math.max(0, wrapper.getBoundingClientRect().top + wrapper.clientTop - body.getBoundingClientRect().top);
|
|
906
|
+
const height = wrapper.clientHeight;
|
|
907
|
+
setViewport((previous) => previous.top === top && previous.height === height ? previous : { top, height });
|
|
908
|
+
}
|
|
909
|
+
});
|
|
806
910
|
};
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
newHeights[realIdx] = h;
|
|
818
|
-
changed = true;
|
|
911
|
+
wrapper.addEventListener("scroll", measureScroll);
|
|
912
|
+
const resize = new ResizeObserver(() => {
|
|
913
|
+
var _a;
|
|
914
|
+
if (wrapper.clientWidth !== width) {
|
|
915
|
+
width = wrapper.clientWidth;
|
|
916
|
+
const renderedRows = ((_a = tableBodyRef.current) == null ? void 0 : _a.querySelectorAll(":scope > tr[data-table-row-key]")) ?? [];
|
|
917
|
+
setHeights(new Map(Array.from(renderedRows, (row) => [
|
|
918
|
+
row.getAttribute("data-table-row-key"),
|
|
919
|
+
row.getBoundingClientRect().height
|
|
920
|
+
])));
|
|
819
921
|
}
|
|
922
|
+
measureScroll();
|
|
820
923
|
});
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
let getEstimatedHeight = function(index, heights) {
|
|
828
|
-
for (let i = index - 1; i >= 0; i--) {
|
|
829
|
-
if (heights[i] > 0) return heights[i];
|
|
830
|
-
}
|
|
831
|
-
for (let i = index + 1; i < heights.length; i++) {
|
|
832
|
-
if (heights[i] > 0) return heights[i];
|
|
833
|
-
}
|
|
834
|
-
return 0;
|
|
924
|
+
resize.observe(wrapper);
|
|
925
|
+
measureScroll();
|
|
926
|
+
return () => {
|
|
927
|
+
cancelAnimationFrame(frame);
|
|
928
|
+
wrapper.removeEventListener("scroll", measureScroll);
|
|
929
|
+
resize.disconnect();
|
|
835
930
|
};
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
931
|
+
}, [tableWrapperRef, virtual, rows]);
|
|
932
|
+
useLayoutEffect(() => {
|
|
933
|
+
if (!virtual || !tableBodyRef.current) return;
|
|
934
|
+
const observer = new ResizeObserver((entries) => {
|
|
935
|
+
setHeights((previous) => {
|
|
936
|
+
const next = new Map(previous);
|
|
937
|
+
let changed = false;
|
|
938
|
+
entries.forEach(({ target }) => {
|
|
939
|
+
const key = target.getAttribute("data-table-row-key");
|
|
940
|
+
const height = target.getBoundingClientRect().height;
|
|
941
|
+
if (height > 0 && previous.get(key) !== height) {
|
|
942
|
+
next.set(key, height);
|
|
943
|
+
changed = true;
|
|
944
|
+
}
|
|
945
|
+
});
|
|
946
|
+
return changed ? next : previous;
|
|
947
|
+
});
|
|
948
|
+
});
|
|
949
|
+
tableBodyRef.current.querySelectorAll(":scope > tr[data-table-row-key]").forEach((row) => observer.observe(row));
|
|
950
|
+
return () => observer.disconnect();
|
|
951
|
+
}, [virtual, visibleRows]);
|
|
952
|
+
return /* @__PURE__ */ jsx(TruncateContext.Provider, { value: truncateAt, children: /* @__PURE__ */ jsxs(
|
|
953
|
+
"tbody",
|
|
954
|
+
{
|
|
955
|
+
ref: tableBodyRef,
|
|
956
|
+
style: st,
|
|
957
|
+
...props,
|
|
958
|
+
className: `${styles$t["f-table-component__table_body"]} ${props.className || ""} ${styles$t[textAlignment]}`,
|
|
959
|
+
children: [
|
|
960
|
+
virtual && start > 0 && /* @__PURE__ */ jsx("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx("td", { colSpan: index.columnCount, style: { height: offsets[start], padding: 0, border: 0 } }) }),
|
|
961
|
+
visibleRows.map((child, index2) => React.isValidElement(child) ? React.cloneElement(child, {
|
|
962
|
+
"data-row-index": start + index2,
|
|
963
|
+
"data-table-row-key": keyOf(child, start + index2)
|
|
964
|
+
}) : child),
|
|
965
|
+
query.trim() && rows.length === 0 && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: index.columnCount, className: styles$t["table-empty"], children: /* @__PURE__ */ jsx("span", { role: "status", children: "Ничего не найдено" }) }) }),
|
|
966
|
+
virtual && end < rows.length && /* @__PURE__ */ jsx("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx("td", { colSpan: index.columnCount, style: { height: offsets[rows.length] - offsets[end], padding: 0, border: 0 } }) })
|
|
967
|
+
]
|
|
968
|
+
}
|
|
969
|
+
) });
|
|
848
970
|
};
|
|
849
971
|
const FTableBody$1 = React.memo(FTableBody);
|
|
850
972
|
const FTable = ({
|
|
@@ -858,10 +980,18 @@ const FTable = ({
|
|
|
858
980
|
// По умолчанию заголовок не "липкий"
|
|
859
981
|
layout = "auto",
|
|
860
982
|
// Добавляем новый пропс с дефолтным значением 'auto'
|
|
983
|
+
searchable = false,
|
|
861
984
|
...props
|
|
862
985
|
}) => {
|
|
863
986
|
const tableWrapperRef = useRef(null);
|
|
864
987
|
const [isStickyState, setIsStickyState] = useState(false);
|
|
988
|
+
const [search, setSearch] = useState("");
|
|
989
|
+
const query = useDeferredValue(searchable ? search : "");
|
|
990
|
+
const [sort, setSort] = useState(null);
|
|
991
|
+
const toggleSort = useCallback((key) => {
|
|
992
|
+
setSort((current) => (current == null ? void 0 : current.key) !== key ? { key, direction: "ascending" } : current.direction === "ascending" ? { key, direction: "descending" } : null);
|
|
993
|
+
}, []);
|
|
994
|
+
const controls = useMemo(() => ({ query, sort, toggleSort }), [query, sort, toggleSort]);
|
|
865
995
|
useEffect(() => {
|
|
866
996
|
const handleScroll = () => {
|
|
867
997
|
if (tableWrapperRef.current && isSticky) {
|
|
@@ -879,45 +1009,66 @@ const FTable = ({
|
|
|
879
1009
|
}
|
|
880
1010
|
};
|
|
881
1011
|
}, []);
|
|
882
|
-
return /* @__PURE__ */
|
|
883
|
-
"div",
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
"
|
|
1012
|
+
return /* @__PURE__ */ jsxs(TableControlsContext.Provider, { value: controls, children: [
|
|
1013
|
+
searchable && /* @__PURE__ */ jsx("div", { className: styles$t["table-tools"], children: /* @__PURE__ */ jsxs("label", { className: styles$t["table-search"], children: [
|
|
1014
|
+
/* @__PURE__ */ jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: [
|
|
1015
|
+
/* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "7" }),
|
|
1016
|
+
/* @__PURE__ */ jsx("path", { d: "m20 20-3.5-3.5" })
|
|
1017
|
+
] }),
|
|
1018
|
+
/* @__PURE__ */ jsx(
|
|
1019
|
+
"input",
|
|
890
1020
|
{
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
{
|
|
900
|
-
...headProps,
|
|
901
|
-
isSticky: isStickyState
|
|
902
|
-
}
|
|
903
|
-
);
|
|
904
|
-
}
|
|
905
|
-
if (React.isValidElement(child) && child.type === FTableBody$1) {
|
|
906
|
-
const headProps = child.props;
|
|
907
|
-
return React.cloneElement(
|
|
908
|
-
child,
|
|
909
|
-
{
|
|
910
|
-
...headProps,
|
|
911
|
-
tableWrapperRef
|
|
912
|
-
}
|
|
913
|
-
);
|
|
914
|
-
}
|
|
915
|
-
return child;
|
|
916
|
-
})
|
|
1021
|
+
type: "search",
|
|
1022
|
+
"aria-label": "Поиск по таблице",
|
|
1023
|
+
placeholder: "Поиск",
|
|
1024
|
+
value: search,
|
|
1025
|
+
onChange: (event) => setSearch(event.target.value),
|
|
1026
|
+
onKeyDown: (event) => {
|
|
1027
|
+
if (event.key === "Escape") setSearch("");
|
|
1028
|
+
}
|
|
917
1029
|
}
|
|
918
1030
|
)
|
|
919
|
-
}
|
|
920
|
-
|
|
1031
|
+
] }) }),
|
|
1032
|
+
/* @__PURE__ */ jsx(
|
|
1033
|
+
"div",
|
|
1034
|
+
{
|
|
1035
|
+
ref: tableWrapperRef,
|
|
1036
|
+
className: styles$t["f-table-component"],
|
|
1037
|
+
style: { overflowX, overflowY },
|
|
1038
|
+
children: /* @__PURE__ */ jsx(
|
|
1039
|
+
"table",
|
|
1040
|
+
{
|
|
1041
|
+
style: { ...st, tableLayout: layout },
|
|
1042
|
+
...props,
|
|
1043
|
+
className: `table ${styles$t["f-table-component__table"]} ${props.className || ""}`,
|
|
1044
|
+
children: React.Children.map(children, (child) => {
|
|
1045
|
+
if (React.isValidElement(child) && child.type === FTableHead) {
|
|
1046
|
+
const headProps = child.props;
|
|
1047
|
+
return React.cloneElement(
|
|
1048
|
+
child,
|
|
1049
|
+
{
|
|
1050
|
+
...headProps,
|
|
1051
|
+
isSticky: isStickyState
|
|
1052
|
+
}
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
if (React.isValidElement(child) && child.type === FTableBody$1) {
|
|
1056
|
+
const headProps = child.props;
|
|
1057
|
+
return React.cloneElement(
|
|
1058
|
+
child,
|
|
1059
|
+
{
|
|
1060
|
+
...headProps,
|
|
1061
|
+
tableWrapperRef
|
|
1062
|
+
}
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
return child;
|
|
1066
|
+
})
|
|
1067
|
+
}
|
|
1068
|
+
)
|
|
1069
|
+
}
|
|
1070
|
+
)
|
|
1071
|
+
] });
|
|
921
1072
|
};
|
|
922
1073
|
const FTableRow = ({
|
|
923
1074
|
st,
|
|
@@ -956,8 +1107,12 @@ const FTableHeaderCell = ({
|
|
|
956
1107
|
// По умолчанию выравнивания нет
|
|
957
1108
|
width = "auto",
|
|
958
1109
|
// По умолчанию ширина автоматическая
|
|
1110
|
+
sortKey,
|
|
959
1111
|
...props
|
|
960
1112
|
}) => {
|
|
1113
|
+
const { sort, toggleSort } = useContext(TableControlsContext);
|
|
1114
|
+
const sortable = Boolean(sortKey && toggleSort);
|
|
1115
|
+
const direction = (sort == null ? void 0 : sort.key) === sortKey ? sort == null ? void 0 : sort.direction : void 0;
|
|
961
1116
|
const style = {
|
|
962
1117
|
textAlign: textAlignment,
|
|
963
1118
|
width,
|
|
@@ -969,12 +1124,101 @@ const FTableHeaderCell = ({
|
|
|
969
1124
|
rowSpan: row && row > 1 ? row : void 0,
|
|
970
1125
|
colSpan: col && col > 1 ? col : void 0,
|
|
971
1126
|
...props,
|
|
1127
|
+
"aria-sort": sortable ? direction ?? "none" : props["aria-sort"],
|
|
972
1128
|
className: `${styles$t["f-table-component__table_header-cell"]} ${props.className || ""}`,
|
|
973
1129
|
style,
|
|
974
|
-
children
|
|
1130
|
+
children: sortable ? /* @__PURE__ */ jsxs(
|
|
1131
|
+
"button",
|
|
1132
|
+
{
|
|
1133
|
+
type: "button",
|
|
1134
|
+
className: styles$t["table-sort"],
|
|
1135
|
+
onClick: () => toggleSort == null ? void 0 : toggleSort(sortKey),
|
|
1136
|
+
title: direction === "ascending" ? "По убыванию" : direction === "descending" ? "Исходный порядок" : "По возрастанию",
|
|
1137
|
+
children: [
|
|
1138
|
+
children,
|
|
1139
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: direction === "ascending" ? "↑" : direction === "descending" ? "↓" : "↕" })
|
|
1140
|
+
]
|
|
1141
|
+
}
|
|
1142
|
+
) : children
|
|
975
1143
|
}
|
|
976
1144
|
);
|
|
977
1145
|
};
|
|
1146
|
+
function FTableTextTooltip({ text, enabled, children }) {
|
|
1147
|
+
const id = useId();
|
|
1148
|
+
const anchor = useRef(null);
|
|
1149
|
+
const panel2 = useRef(null);
|
|
1150
|
+
const timer = useRef();
|
|
1151
|
+
const [open, setOpen] = useState(false);
|
|
1152
|
+
const [position, setPosition] = useState({ top: 0, left: 0 });
|
|
1153
|
+
const visible2 = enabled && open;
|
|
1154
|
+
const show = () => {
|
|
1155
|
+
clearTimeout(timer.current);
|
|
1156
|
+
setOpen(true);
|
|
1157
|
+
};
|
|
1158
|
+
const hide2 = () => {
|
|
1159
|
+
clearTimeout(timer.current);
|
|
1160
|
+
timer.current = setTimeout(() => setOpen(false), 150);
|
|
1161
|
+
};
|
|
1162
|
+
useEffect(() => () => clearTimeout(timer.current), []);
|
|
1163
|
+
useEffect(() => {
|
|
1164
|
+
if (!enabled) setOpen(false);
|
|
1165
|
+
}, [enabled]);
|
|
1166
|
+
useLayoutEffect(() => {
|
|
1167
|
+
if (!visible2) return;
|
|
1168
|
+
const update = () => {
|
|
1169
|
+
if (!anchor.current || !panel2.current) return;
|
|
1170
|
+
const rect = anchor.current.getBoundingClientRect();
|
|
1171
|
+
const tip = panel2.current.getBoundingClientRect();
|
|
1172
|
+
const top = rect.bottom + 8 + tip.height <= window.innerHeight - 10 ? rect.bottom + 8 : rect.top - tip.height - 8;
|
|
1173
|
+
setPosition({
|
|
1174
|
+
top: Math.max(10, Math.min(top, window.innerHeight - tip.height - 10)),
|
|
1175
|
+
left: Math.max(10, Math.min(rect.left, window.innerWidth - tip.width - 10))
|
|
1176
|
+
});
|
|
1177
|
+
};
|
|
1178
|
+
const escape = (event) => {
|
|
1179
|
+
if (event.key === "Escape") {
|
|
1180
|
+
clearTimeout(timer.current);
|
|
1181
|
+
setOpen(false);
|
|
1182
|
+
}
|
|
1183
|
+
};
|
|
1184
|
+
update();
|
|
1185
|
+
window.addEventListener("resize", update);
|
|
1186
|
+
window.addEventListener("scroll", update, true);
|
|
1187
|
+
document.addEventListener("keydown", escape);
|
|
1188
|
+
return () => {
|
|
1189
|
+
window.removeEventListener("resize", update);
|
|
1190
|
+
window.removeEventListener("scroll", update, true);
|
|
1191
|
+
document.removeEventListener("keydown", escape);
|
|
1192
|
+
};
|
|
1193
|
+
}, [visible2, text]);
|
|
1194
|
+
return /* @__PURE__ */ jsxs(
|
|
1195
|
+
"span",
|
|
1196
|
+
{
|
|
1197
|
+
ref: anchor,
|
|
1198
|
+
className: styles$t["cell-tooltip-anchor"],
|
|
1199
|
+
onMouseEnter: show,
|
|
1200
|
+
onMouseLeave: hide2,
|
|
1201
|
+
onFocus: show,
|
|
1202
|
+
onBlur: hide2,
|
|
1203
|
+
children: [
|
|
1204
|
+
cloneElement(children, { "aria-describedby": visible2 ? id : void 0 }),
|
|
1205
|
+
visible2 && createPortal(/* @__PURE__ */ jsx(
|
|
1206
|
+
"div",
|
|
1207
|
+
{
|
|
1208
|
+
ref: panel2,
|
|
1209
|
+
id,
|
|
1210
|
+
role: "tooltip",
|
|
1211
|
+
className: styles$t["cell-tooltip"],
|
|
1212
|
+
style: position,
|
|
1213
|
+
onMouseEnter: show,
|
|
1214
|
+
onMouseLeave: hide2,
|
|
1215
|
+
children: text
|
|
1216
|
+
}
|
|
1217
|
+
), document.body)
|
|
1218
|
+
]
|
|
1219
|
+
}
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
978
1222
|
const FTableDataCell = ({
|
|
979
1223
|
st,
|
|
980
1224
|
row,
|
|
@@ -984,8 +1228,20 @@ const FTableDataCell = ({
|
|
|
984
1228
|
// По умолчанию выравнивания нет
|
|
985
1229
|
height = "auto",
|
|
986
1230
|
// По умолчанию высота автоматическая
|
|
1231
|
+
truncate,
|
|
1232
|
+
sortKey: _sortKey,
|
|
1233
|
+
sortValue: _sortValue,
|
|
1234
|
+
searchValue: _searchValue,
|
|
987
1235
|
...props
|
|
988
1236
|
}) => {
|
|
1237
|
+
const truncateAt = useContext(TruncateContext);
|
|
1238
|
+
const [expanded, setExpanded] = useState(false);
|
|
1239
|
+
const textId = useId();
|
|
1240
|
+
const limit = truncateAt !== void 0 && Number.isFinite(truncateAt) && truncateAt >= 1 ? Math.floor(truncateAt) : void 0;
|
|
1241
|
+
const text = typeof children === "string" || typeof children === "number" ? String(children) : null;
|
|
1242
|
+
const characters = text === null ? [] : Array.from(text);
|
|
1243
|
+
const threshold = limit ?? 80;
|
|
1244
|
+
const collapsible = truncate !== false && (truncate === true || limit !== void 0) && characters.length > threshold;
|
|
989
1245
|
const style = {
|
|
990
1246
|
textAlign: textAlignment,
|
|
991
1247
|
height,
|
|
@@ -999,7 +1255,24 @@ const FTableDataCell = ({
|
|
|
999
1255
|
...props,
|
|
1000
1256
|
className: `${styles$t["f-table-component__table_body-cell"]} ${props.className || ""}`,
|
|
1001
1257
|
style,
|
|
1002
|
-
children
|
|
1258
|
+
children: collapsible ? /* @__PURE__ */ jsx(FTableTextTooltip, { text, enabled: !expanded, children: /* @__PURE__ */ jsxs(
|
|
1259
|
+
"button",
|
|
1260
|
+
{
|
|
1261
|
+
type: "button",
|
|
1262
|
+
className: styles$t["cell-text"],
|
|
1263
|
+
"aria-expanded": expanded,
|
|
1264
|
+
"aria-controls": textId,
|
|
1265
|
+
"data-expanded": expanded,
|
|
1266
|
+
onClick: () => setExpanded((value) => !value),
|
|
1267
|
+
children: [
|
|
1268
|
+
/* @__PURE__ */ jsxs("span", { className: styles$t["cell-preview"], "aria-hidden": "true", children: [
|
|
1269
|
+
characters.slice(0, threshold).join(""),
|
|
1270
|
+
"…"
|
|
1271
|
+
] }),
|
|
1272
|
+
/* @__PURE__ */ jsx("span", { className: styles$t["cell-reveal"], children: /* @__PURE__ */ jsx("span", { id: textId, children: text }) })
|
|
1273
|
+
]
|
|
1274
|
+
}
|
|
1275
|
+
) }) : children
|
|
1003
1276
|
}
|
|
1004
1277
|
);
|
|
1005
1278
|
};
|
|
@@ -4032,6 +4305,87 @@ const FSegmentedControl = ({
|
|
|
4032
4305
|
] }) });
|
|
4033
4306
|
};
|
|
4034
4307
|
FSegmentedControl.displayName = "FSegmentedControl";
|
|
4308
|
+
function FTableActions({ children, className, onClick, ...props }) {
|
|
4309
|
+
const id = useId();
|
|
4310
|
+
const menu = useRef(null);
|
|
4311
|
+
const trigger = useRef(null);
|
|
4312
|
+
const [open, setOpen] = useState(false);
|
|
4313
|
+
useEffect(() => {
|
|
4314
|
+
const panel2 = menu.current;
|
|
4315
|
+
const toggle = () => {
|
|
4316
|
+
var _a, _b;
|
|
4317
|
+
const visible2 = panel2.matches(":popover-open");
|
|
4318
|
+
setOpen(visible2);
|
|
4319
|
+
if (visible2) (_a = panel2.querySelector('button:not(:disabled), a[href], input, [tabindex="0"]')) == null ? void 0 : _a.focus();
|
|
4320
|
+
else if (panel2.contains(document.activeElement)) (_b = trigger.current) == null ? void 0 : _b.focus();
|
|
4321
|
+
};
|
|
4322
|
+
panel2.addEventListener("toggle", toggle);
|
|
4323
|
+
return () => panel2.removeEventListener("toggle", toggle);
|
|
4324
|
+
}, []);
|
|
4325
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4326
|
+
/* @__PURE__ */ jsx(
|
|
4327
|
+
"button",
|
|
4328
|
+
{
|
|
4329
|
+
ref: trigger,
|
|
4330
|
+
type: "button",
|
|
4331
|
+
"aria-label": "Действия",
|
|
4332
|
+
...props,
|
|
4333
|
+
className: `${styles$t.more} ${className || ""}`,
|
|
4334
|
+
"aria-expanded": open,
|
|
4335
|
+
"aria-controls": id,
|
|
4336
|
+
onClick: (event) => {
|
|
4337
|
+
event.stopPropagation();
|
|
4338
|
+
onClick == null ? void 0 : onClick(event);
|
|
4339
|
+
if (event.defaultPrevented || !menu.current) return;
|
|
4340
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
4341
|
+
const panel2 = menu.current;
|
|
4342
|
+
panel2.style.maxHeight = `${Math.max(0, (rect.bottom > window.innerHeight / 2 ? rect.top : window.innerHeight - rect.bottom) - 12)}px`;
|
|
4343
|
+
panel2.style.left = `${Math.max(8, Math.min(rect.right - 178, window.innerWidth - 186))}px`;
|
|
4344
|
+
panel2.style.top = `${Math.max(8, rect.bottom + 4)}px`;
|
|
4345
|
+
panel2.style.bottom = "auto";
|
|
4346
|
+
if (rect.bottom > window.innerHeight / 2) {
|
|
4347
|
+
panel2.style.top = "auto";
|
|
4348
|
+
panel2.style.bottom = `${Math.max(8, window.innerHeight - rect.top + 4)}px`;
|
|
4349
|
+
}
|
|
4350
|
+
if (open) panel2.hidePopover();
|
|
4351
|
+
else panel2.showPopover();
|
|
4352
|
+
},
|
|
4353
|
+
children: /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: [
|
|
4354
|
+
/* @__PURE__ */ jsx("circle", { cx: "5", cy: "12", r: "1.6" }),
|
|
4355
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "1.6" }),
|
|
4356
|
+
/* @__PURE__ */ jsx("circle", { cx: "19", cy: "12", r: "1.6" })
|
|
4357
|
+
] })
|
|
4358
|
+
}
|
|
4359
|
+
),
|
|
4360
|
+
/* @__PURE__ */ jsx(
|
|
4361
|
+
"div",
|
|
4362
|
+
{
|
|
4363
|
+
ref: menu,
|
|
4364
|
+
id,
|
|
4365
|
+
...{ popover: "auto" },
|
|
4366
|
+
className: styles$t["row-menu"],
|
|
4367
|
+
onKeyDown: (event) => {
|
|
4368
|
+
var _a, _b;
|
|
4369
|
+
if (event.key === "Escape") {
|
|
4370
|
+
event.preventDefault();
|
|
4371
|
+
event.stopPropagation();
|
|
4372
|
+
(_a = trigger.current) == null ? void 0 : _a.focus();
|
|
4373
|
+
(_b = menu.current) == null ? void 0 : _b.hidePopover();
|
|
4374
|
+
}
|
|
4375
|
+
},
|
|
4376
|
+
onClick: (event) => {
|
|
4377
|
+
var _a, _b;
|
|
4378
|
+
event.stopPropagation();
|
|
4379
|
+
if (!event.defaultPrevented && event.target.closest("button:not(:disabled), a[href]")) {
|
|
4380
|
+
(_a = trigger.current) == null ? void 0 : _a.focus();
|
|
4381
|
+
(_b = menu.current) == null ? void 0 : _b.hidePopover();
|
|
4382
|
+
}
|
|
4383
|
+
},
|
|
4384
|
+
children
|
|
4385
|
+
}
|
|
4386
|
+
)
|
|
4387
|
+
] });
|
|
4388
|
+
}
|
|
4035
4389
|
const FPlusIcon = React.forwardRef(({
|
|
4036
4390
|
color = "primary",
|
|
4037
4391
|
size = 30,
|
|
@@ -8954,6 +9308,7 @@ export {
|
|
|
8954
9308
|
FStack,
|
|
8955
9309
|
FTab,
|
|
8956
9310
|
FTable,
|
|
9311
|
+
FTableActions,
|
|
8957
9312
|
FTableBody$1 as FTableBody,
|
|
8958
9313
|
FTableDataCell,
|
|
8959
9314
|
FTableFooter,
|