@ioca/react 1.5.6 → 1.5.7

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/lib/index.js CHANGED
@@ -4971,14 +4971,104 @@ const Swiper = ((props) => {
4971
4971
  });
4972
4972
  Swiper.Item = Item$1;
4973
4973
 
4974
+ const TabsContents = (props) => {
4975
+ const { tabs, activeKey, cachedTabKeySet, getContent } = props;
4976
+ return (jsx("div", { className: "i-tab-contents", children: tabs.map((tab, i) => {
4977
+ const key = tab.key ?? `${i}`;
4978
+ const content = getContent(key);
4979
+ const isActive = activeKey === key;
4980
+ const show = isActive || cachedTabKeySet.has(key);
4981
+ return (show && (jsx("div", { className: classNames("i-tab-content", {
4982
+ "i-tab-active": isActive,
4983
+ }), role: "tabpanel", "aria-hidden": !isActive, children: content }, key)));
4984
+ }) }));
4985
+ };
4986
+ var TabsContents$1 = memo(TabsContents);
4987
+
4988
+ const emptyBarStyle = {
4989
+ height: 0,
4990
+ width: 0,
4991
+ };
4992
+ const defaultRenderMore = () => (jsx(Button, { flat: true, square: true, size: "small", children: jsx(Icon, { icon: jsx(MoreHorizRound, {}) }) }));
4993
+ const isSameTabs = (prev, next) => prev.length === next.length &&
4994
+ prev.every((tab, index) => {
4995
+ const target = next[index];
4996
+ return (tab.key === target.key &&
4997
+ tab.title === target.title &&
4998
+ tab.keepDOM === target.keepDOM &&
4999
+ tab.closable === target.closable &&
5000
+ tab.intersecting === target.intersecting);
5001
+ });
5002
+ const getParsedTabs = (items, children) => {
5003
+ const contents = new Map();
5004
+ if (!items) {
5005
+ const tabs = Children.map(children, (node, i) => {
5006
+ const { key, props: nodeProps } = node;
5007
+ const { title, children: tabChildren, content, keepDOM, closable, } = nodeProps;
5008
+ const tabKey = String(key ?? i);
5009
+ contents.set(tabKey, tabChildren ?? content);
5010
+ return {
5011
+ key: tabKey,
5012
+ title,
5013
+ keepDOM,
5014
+ closable,
5015
+ };
5016
+ }) ?? [];
5017
+ return {
5018
+ tabs,
5019
+ contents,
5020
+ keys: new Set(tabs.map((tab) => String(tab.key))),
5021
+ };
5022
+ }
5023
+ const tabs = items.map((item, i) => {
5024
+ if (["string", "number"].includes(typeof item)) {
5025
+ const key = String(item);
5026
+ return { key, title: item };
5027
+ }
5028
+ const key = String(item.key ?? i);
5029
+ contents.set(key, item.content);
5030
+ const { content, ...rest } = item;
5031
+ return {
5032
+ ...rest,
5033
+ key,
5034
+ };
5035
+ });
5036
+ return {
5037
+ tabs,
5038
+ contents,
5039
+ keys: new Set(tabs.map((tab) => String(tab.key))),
5040
+ };
5041
+ };
5042
+
4974
5043
  const Item = (props) => {
4975
5044
  return jsx(Fragment, {});
4976
5045
  };
4977
5046
 
5047
+ const TabsNavs = (props) => {
5048
+ const { tabs, moreTabs, activeKey, vertical, overflow, hideMore, navsJustify = "start", bar, barClass, barStyle, navsRef, renderMore, setNavRef, onOpen, onClose, onMoreTabClick, onKeyAction, } = props;
5049
+ return (jsxs(Fragment, { children: [jsxs("div", { ref: navsRef, className: classNames("i-tab-navs", `justify-${navsJustify}`), role: "tablist", "aria-orientation": vertical ? "vertical" : "horizontal", children: [tabs.map((tab, i) => {
5050
+ const { title, key = `${i}`, closable } = tab;
5051
+ const isActive = activeKey === key;
5052
+ return (jsxs("a", { ref: (node) => setNavRef(i, node), className: classNames("i-tab-nav", {
5053
+ "i-tab-active": isActive,
5054
+ }), role: "tab", tabIndex: isActive ? 0 : -1, "aria-selected": isActive, onClick: () => onOpen(key), onKeyDown: (e) => onKeyAction(e, () => onOpen(key)), children: [title, closable && (jsx(Helpericon, { as: "i", active: true, className: "i-tab-nav-close", role: "button", tabIndex: 0, "aria-label": "\u5173\u95ED\u6807\u7B7E\u9875", onClick: (e) => {
5055
+ e.preventDefault();
5056
+ e.stopPropagation();
5057
+ onClose(key);
5058
+ }, onKeyDown: (e) => onKeyAction(e, () => onClose(key)) }))] }, key));
5059
+ }), bar && (jsx("span", { className: classNames("i-tab-navs-bar", barClass), style: barStyle }))] }), !hideMore && overflow && moreTabs.length > 0 && (jsx(Popup, { arrow: false, position: vertical ? "right" : "bottom", align: "end", touchable: true, hideDelay: 500, content: jsx("div", { className: "i-tabs-morelist pd-4", children: moreTabs.map((tab, i) => {
5060
+ const { key = `${i}`, title } = tab;
5061
+ const isActive = activeKey === key;
5062
+ return (jsx("a", { className: classNames("i-tab-nav", {
5063
+ "i-tab-active": isActive,
5064
+ }), role: "button", tabIndex: 0, onClick: () => onMoreTabClick(key), onKeyDown: (e) => onKeyAction(e, () => onMoreTabClick(key)), children: title }, key));
5065
+ }) }), children: renderMore(moreTabs) }))] }));
5066
+ };
5067
+ var TabsNavs$1 = memo(TabsNavs);
5068
+
4978
5069
  const Tabs = ((props) => {
4979
- const { ref, active, tabs: items, type = "default", prepend, append, children, className, vertical, toggable, navsJustify = "start", bar = true, hideMore, barClass, renderMore = () => (jsx(Button, { flat: true, square: true, size: "small", children: jsx(Icon, { icon: jsx(MoreHorizRound, {}) }) })), onTabChange, ...rest } = props;
5070
+ const { ref, active, tabs: items, type = "default", prepend, append, children, className, vertical, toggable, navsJustify = "start", navsClass, bar = true, hideMore, barClass, renderMore = defaultRenderMore, onTabChange, ...rest } = props;
4980
5071
  const navRefs = useRef([]);
4981
- const barRef = useRef(null);
4982
5072
  const navsRef = useRef(null);
4983
5073
  const contentsRef = useRef(new Map());
4984
5074
  const [activeKey, setActiveKey] = useState(active);
@@ -4995,42 +5085,36 @@ const Tabs = ((props) => {
4995
5085
  activeKeyRef.current = activeKey;
4996
5086
  const prevActiveKeyRef = useRef(prevActiveKey);
4997
5087
  prevActiveKeyRef.current = prevActiveKey;
5088
+ const sourceKeysRef = useRef(new Set());
5089
+ const hiddenSourceKeysRef = useRef(new Set());
5090
+ const parsedTabs = useMemo(() => getParsedTabs(items, children), [children, items]);
4998
5091
  useEffect(() => {
4999
- contentsRef.current.clear();
5000
- if (!items) {
5001
- if (!children) {
5002
- setTabs([]);
5003
- return;
5092
+ const prevContents = new Map(contentsRef.current);
5093
+ const nextContents = new Map(parsedTabs.contents);
5094
+ const sourceTabs = parsedTabs.tabs;
5095
+ const sourceKeys = parsedTabs.keys;
5096
+ hiddenSourceKeysRef.current.forEach((key) => {
5097
+ if (!sourceKeys.has(key)) {
5098
+ hiddenSourceKeysRef.current.delete(key);
5004
5099
  }
5005
- setTabs(Children.map(children, (node, i) => {
5006
- const { key, props: nodeProps } = node;
5007
- const { title, children: tabChildren, content, keepDOM, closable, } = nodeProps;
5008
- const tabKey = String(key ?? i);
5009
- contentsRef.current.set(tabKey, tabChildren ?? content);
5010
- return {
5011
- key: tabKey,
5012
- title,
5013
- keepDOM,
5014
- closable,
5015
- };
5016
- }) ?? []);
5017
- return;
5018
- }
5019
- setTabs(items.map((item, i) => {
5020
- if (["string", "number"].includes(typeof item)) {
5021
- const key = String(item);
5022
- return { key, title: item };
5023
- }
5024
- const key = String(item.key ?? i);
5025
- contentsRef.current.set(key, item.content);
5026
- const { content, ...rest } = item;
5027
- return {
5028
- ...rest,
5029
- key,
5030
- };
5031
- }));
5032
- }, [children, items]);
5033
- const add = (tab) => {
5100
+ });
5101
+ const dynamicTabs = tabsRef.current.filter((tab) => {
5102
+ const key = String(tab.key);
5103
+ return !sourceKeysRef.current.has(key) && !sourceKeys.has(key);
5104
+ });
5105
+ dynamicTabs.forEach((tab) => {
5106
+ const key = String(tab.key);
5107
+ nextContents.set(key, prevContents.get(key));
5108
+ });
5109
+ sourceKeysRef.current = sourceKeys;
5110
+ contentsRef.current = nextContents;
5111
+ const nextTabs = [
5112
+ ...sourceTabs.filter((tab) => !hiddenSourceKeysRef.current.has(String(tab.key))),
5113
+ ...dynamicTabs,
5114
+ ];
5115
+ setTabs((currentTabs) => isSameTabs(currentTabs, nextTabs) ? currentTabs : nextTabs);
5116
+ }, [parsedTabs]);
5117
+ const add = (tab, position) => {
5034
5118
  const currentTabs = tabsRef.current;
5035
5119
  const tkey = String(tab.key ?? currentTabs.length);
5036
5120
  const i = currentTabs.findIndex((t) => t.key === tkey);
@@ -5040,7 +5124,14 @@ const Tabs = ((props) => {
5040
5124
  }
5041
5125
  contentsRef.current.set(tkey, tab.content);
5042
5126
  const { content, ...rest } = tab;
5043
- setTabs((ts) => [...ts, { ...rest, key: tkey }]);
5127
+ setTabs((ts) => {
5128
+ const nextTabs = [...ts];
5129
+ const index = position === undefined
5130
+ ? nextTabs.length
5131
+ : Math.max(0, Math.min(position, nextTabs.length));
5132
+ nextTabs.splice(index, 0, { ...rest, key: tkey });
5133
+ return nextTabs;
5134
+ });
5044
5135
  open(tkey);
5045
5136
  };
5046
5137
  const close = (key) => {
@@ -5048,7 +5139,12 @@ const Tabs = ((props) => {
5048
5139
  const i = currentTabs.findIndex((t) => t.key === key);
5049
5140
  if (i < 0)
5050
5141
  return;
5051
- contentsRef.current.delete(key);
5142
+ if (sourceKeysRef.current.has(key)) {
5143
+ hiddenSourceKeysRef.current.add(key);
5144
+ }
5145
+ else {
5146
+ contentsRef.current.delete(key);
5147
+ }
5052
5148
  const nextTabs = [...currentTabs];
5053
5149
  nextTabs.splice(i, 1);
5054
5150
  setTabs(nextTabs);
@@ -5065,10 +5161,7 @@ const Tabs = ((props) => {
5065
5161
  onTabChange?.(undefined, activeKey);
5066
5162
  setPrevActiveKey(activeKey);
5067
5163
  setActiveKey(undefined);
5068
- setBarStyle({
5069
- height: 0,
5070
- width: 0,
5071
- });
5164
+ setBarStyle(emptyBarStyle);
5072
5165
  return;
5073
5166
  }
5074
5167
  if (nextKey === activeKey) {
@@ -5076,10 +5169,7 @@ const Tabs = ((props) => {
5076
5169
  return;
5077
5170
  onTabChange?.(undefined, key);
5078
5171
  setActiveKey(undefined);
5079
- setBarStyle({
5080
- height: 0,
5081
- width: 0,
5082
- });
5172
+ setBarStyle(emptyBarStyle);
5083
5173
  return;
5084
5174
  }
5085
5175
  setPrevActiveKey(activeKey);
@@ -5105,6 +5195,10 @@ const Tabs = ((props) => {
5105
5195
  open(key);
5106
5196
  scrollToTab(key);
5107
5197
  };
5198
+ const setNavRef = (index, node) => {
5199
+ navRefs.current[index] = node;
5200
+ };
5201
+ const getContent = (key) => contentsRef.current.get(key);
5108
5202
  useEffect(() => {
5109
5203
  if (!size || hideMore || !observe || !unobserve)
5110
5204
  return;
@@ -5128,7 +5222,7 @@ const Tabs = ((props) => {
5128
5222
  return;
5129
5223
  }
5130
5224
  const observed = [];
5131
- navRefs.current.map((nav, i) => {
5225
+ navRefs.current.forEach((nav, i) => {
5132
5226
  if (!nav)
5133
5227
  return;
5134
5228
  observed.push(nav);
@@ -5143,7 +5237,7 @@ const Tabs = ((props) => {
5143
5237
  });
5144
5238
  });
5145
5239
  return () => {
5146
- observed.map((el) => unobserve(el));
5240
+ observed.forEach((el) => unobserve(el));
5147
5241
  };
5148
5242
  }, [size, hideMore, tabs.length, observe, unobserve]);
5149
5243
  useEffect(() => {
@@ -5165,7 +5259,7 @@ const Tabs = ((props) => {
5165
5259
  const { offsetHeight, offsetLeft, offsetTop, offsetWidth } = nav;
5166
5260
  const isLine = type === "line";
5167
5261
  setBarStyle({
5168
- height: !vertical && isLine ? ".25em" : ".8em",
5262
+ height: !vertical && isLine ? ".25em" : offsetHeight * 0.5,
5169
5263
  width: vertical && isLine ? ".25em" : offsetWidth,
5170
5264
  transform: `translate(${offsetLeft}px, ${offsetTop}px)`,
5171
5265
  });
@@ -5177,7 +5271,8 @@ const Tabs = ((props) => {
5177
5271
  useEffect(() => {
5178
5272
  if (active === undefined || activeKey === active)
5179
5273
  return;
5180
- open(active);
5274
+ setPrevActiveKey(activeKey);
5275
+ setActiveKey(active);
5181
5276
  }, [active]);
5182
5277
  useEffect(() => {
5183
5278
  if (!navsRef.current || vertical)
@@ -5206,37 +5301,13 @@ const Tabs = ((props) => {
5206
5301
  add,
5207
5302
  navs: navsRef,
5208
5303
  }));
5209
- const moreTabs = !hideMore && overflow
5304
+ const cachedTabKeySet = useMemo(() => new Set(cachedTabs), [cachedTabs]);
5305
+ const moreTabs = useMemo(() => !hideMore && overflow
5210
5306
  ? tabs.filter((tab) => tab.intersecting === false)
5211
- : [];
5212
- return (jsxs("div", { className: classNames("i-tabs", { flex: vertical, [`i-tabs-${type}`]: type !== "default" }, className), ...rest, children: [jsxs("div", { className: classNames("i-tab-navs-container", {
5307
+ : [], [hideMore, overflow, tabs]);
5308
+ return (jsxs("div", { className: classNames("i-tabs", { flex: vertical, [`i-tabs-${type}`]: type !== "default" }, className), ...rest, children: [jsxs("div", { className: classNames("i-tab-navs-container", navsClass, {
5213
5309
  "i-tab-navs-vertical": vertical,
5214
- }), children: [prepend, jsxs("div", { ref: navsRef, className: classNames("i-tab-navs", `justify-${navsJustify}`), role: "tablist", "aria-orientation": vertical ? "vertical" : "horizontal", children: [tabs.map((tab, i) => {
5215
- const { title, key = `${i}`, closable } = tab;
5216
- const isActive = activeKey === key;
5217
- return (jsxs("a", { ref: (ref) => (navRefs.current[i] = ref), className: classNames("i-tab-nav", {
5218
- "i-tab-active": isActive,
5219
- }), role: "tab", tabIndex: isActive ? 0 : -1, "aria-selected": isActive, onClick: () => open(key), onKeyDown: (e) => handleKeyAction(e, () => open(key)), children: [title, closable && (jsx(Helpericon, { as: "i", active: true, className: "i-tab-nav-close", role: "button", tabIndex: 0, "aria-label": "\u5173\u95ED\u6807\u7B7E\u9875", onClick: (e) => {
5220
- e.preventDefault();
5221
- e.stopPropagation();
5222
- close(key);
5223
- }, onKeyDown: (e) => handleKeyAction(e, () => close(key)) }))] }, key));
5224
- }), bar && (jsx("span", { ref: barRef, className: classNames("i-tab-navs-bar", barClass), style: barStyle }))] }), !hideMore && overflow && moreTabs.length > 0 && (jsx(Popup, { arrow: false, position: vertical ? "right" : "bottom", align: "end", touchable: true, hideDelay: 500, content: jsx("div", { className: "i-tabs-morelist pd-4", children: moreTabs.map((tab, i) => {
5225
- const { key = `${i}`, title } = tab;
5226
- const isActive = activeKey === key;
5227
- return (jsx("a", { className: classNames("i-tab-nav", {
5228
- "i-tab-active": isActive,
5229
- }), role: "button", tabIndex: 0, onClick: () => handleMoreTabClick(key), onKeyDown: (e) => handleKeyAction(e, () => handleMoreTabClick(key)), children: title }, key));
5230
- }) }), children: renderMore(moreTabs) })), append] }), jsx("div", { className: "i-tab-contents", children: tabs.map((tab, i) => {
5231
- const key = tab.key ?? `${i}`;
5232
- const content = contentsRef.current.get(key);
5233
- const isActive = activeKey === key;
5234
- const show = isActive ||
5235
- (key !== undefined && cachedTabs.includes(key));
5236
- return (show && (jsx("div", { className: classNames("i-tab-content", {
5237
- "i-tab-active": isActive,
5238
- }), role: "tabpanel", "aria-hidden": !isActive, children: content }, key)));
5239
- }) })] }));
5310
+ }), children: [prepend, jsx(TabsNavs$1, { tabs: tabs, moreTabs: moreTabs, activeKey: activeKey, vertical: vertical, overflow: overflow, hideMore: hideMore, navsJustify: navsJustify, bar: bar, barClass: barClass, barStyle: barStyle, navsRef: navsRef, renderMore: renderMore, setNavRef: setNavRef, onOpen: open, onClose: close, onMoreTabClick: handleMoreTabClick, onKeyAction: handleKeyAction }), append] }), jsx(TabsContents$1, { tabs: tabs, activeKey: activeKey, cachedTabKeySet: cachedTabKeySet, getContent: getContent })] }));
5240
5311
  });
5241
5312
  Tabs.Item = Item;
5242
5313
 
@@ -1,9 +1,8 @@
1
- import { ForwardRefExoticComponent, RefObject, ReactNode, Ref, CSSProperties } from 'react';
1
+ import { ForwardRefExoticComponent, Ref, ReactNode, RefObject, CSSProperties } from 'react';
2
2
  import Item from './item.js';
3
3
 
4
4
  interface ITabItem {
5
5
  key?: string;
6
- props?: any;
7
6
  title?: ReactNode;
8
7
  content?: ReactNode;
9
8
  closable?: boolean;
@@ -12,7 +11,7 @@ interface ITabItem {
12
11
  children?: ReactNode;
13
12
  }
14
13
  interface ITabs {
15
- ref?: RefObject<RefTabs | null>;
14
+ ref?: Ref<RefTabs>;
16
15
  active?: string;
17
16
  tabs?: ITabItem[] | string[];
18
17
  type?: "default" | "line" | "pane";
@@ -24,6 +23,7 @@ interface ITabs {
24
23
  barClass?: string;
25
24
  toggable?: boolean;
26
25
  navsJustify?: "start" | "center" | "end";
26
+ navsClass?: string;
27
27
  className?: string;
28
28
  children?: ReactNode;
29
29
  style?: CSSProperties;
@@ -34,7 +34,7 @@ interface RefTabs {
34
34
  open: (key: string) => void;
35
35
  close: (key: string) => void;
36
36
  add: (tab: ITabItem, position?: number) => void;
37
- navs: Ref<HTMLDivElement>;
37
+ navs: RefObject<HTMLDivElement>;
38
38
  }
39
39
  interface CompositionTabs extends ForwardRefExoticComponent<ITabs> {
40
40
  Item: typeof Item;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ioca/react",
3
- "version": "1.5.06",
3
+ "version": "1.5.07",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite",