@funnest/charts-ui 0.6.14 → 0.6.16

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/context.d.ts CHANGED
@@ -80,6 +80,8 @@ export interface WidgetContext {
80
80
  intervalGroups: readonly IntervalGroup[];
81
81
  searchCategories: readonly SearchCategory[];
82
82
  searchExchanges: readonly SearchExchange[];
83
+ /** Rows per exchange before a "모두 보기" (0: all). */
84
+ searchPreviewPerExchange: number;
83
85
  /** IANA zone used for the status-bar clock (KST for Korean users by default). */
84
86
  displayTimezone: string;
85
87
  showLogo: boolean;
package/dist/i18n.d.ts CHANGED
@@ -124,6 +124,7 @@ export declare const STRINGS: {
124
124
  allSymbols: string;
125
125
  favorites: string;
126
126
  exchanges: string;
127
+ showAllIn: string;
127
128
  noFavorites: string;
128
129
  navigate: string;
129
130
  openAction: string;
@@ -268,6 +269,7 @@ export declare const STRINGS: {
268
269
  allSymbols: string;
269
270
  favorites: string;
270
271
  exchanges: string;
272
+ showAllIn: string;
271
273
  noFavorites: string;
272
274
  navigate: string;
273
275
  openAction: string;
package/dist/index.js CHANGED
@@ -549,6 +549,7 @@ var STRINGS = {
549
549
  allSymbols: "\uC804\uCCB4",
550
550
  favorites: "\uC990\uACA8\uCC3E\uAE30",
551
551
  exchanges: "\uAC70\uB798\uC18C",
552
+ showAllIn: "{exchange} {n}\uAC1C \uBAA8\uB450 \uBCF4\uAE30 \u203A",
552
553
  noFavorites: "\uC990\uACA8\uCC3E\uAE30\uD55C \uC885\uBAA9\uC774 \uC5C6\uC5B4\uC694. \u2606\uB97C \uB20C\uB7EC \uCD94\uAC00\uD574 \uBCF4\uC138\uC694",
553
554
  navigate: "\uC774\uB3D9",
554
555
  openAction: "\uC5F4\uAE30",
@@ -693,6 +694,7 @@ var STRINGS = {
693
694
  allSymbols: "All",
694
695
  favorites: "Favorites",
695
696
  exchanges: "Exchanges",
697
+ showAllIn: "All {n} on {exchange} \u203A",
696
698
  noFavorites: "No favorites yet \u2014 tap \u2606 to add one",
697
699
  navigate: "navigate",
698
700
  openAction: "open",
@@ -1819,6 +1821,8 @@ var DEBOUNCE_MS = 150;
1819
1821
  var RECENT_KEY2 = "recent-symbols";
1820
1822
  var FAVORITE_KEY = "favorite-symbols";
1821
1823
  var MAX_RECENT = 8;
1824
+ var FIRST_ROWS = 40;
1825
+ var MORE_ROWS = 80;
1822
1826
  var displayCode = (symbol) => symbol.slice(symbol.lastIndexOf(":") + 1);
1823
1827
  var toStored = (info) => JSON.parse(JSON.stringify({ ...info, tickSize: 0, price: void 0, changePercent: void 0, sparkline: void 0 }));
1824
1828
  function Highlight({ text, query }) {
@@ -1894,8 +1898,10 @@ function SymbolSearch({ ctx }) {
1894
1898
  const [recent, setRecent] = d2(() => ctx.storage.get(RECENT_KEY2, []));
1895
1899
  const [favorites, setFavorites] = d2(() => ctx.storage.get(FAVORITE_KEY, []));
1896
1900
  const [active, setActive] = d2(0);
1901
+ const [limit, setLimit] = d2(FIRST_ROWS);
1897
1902
  const inputRef = T2(null);
1898
1903
  const listRef = T2(null);
1904
+ const endRef = T2(null);
1899
1905
  A2(() => inputRef.current?.focus(), []);
1900
1906
  A2(() => {
1901
1907
  let alive = true;
@@ -1921,7 +1927,8 @@ function SymbolSearch({ ctx }) {
1921
1927
  }, [query]);
1922
1928
  const matchCategory = ctx.searchCategories.find((c3) => c3.id === category)?.match ?? (() => true);
1923
1929
  const categoryOf = (info) => ctx.searchCategories.find((c3) => c3.id !== "all" && c3.match(info));
1924
- const isFavorite = (info) => favorites.some((f4) => f4.symbol === info.symbol);
1930
+ const favoriteSet = w2(() => new Set(favorites.map((f4) => f4.symbol)), [favorites]);
1931
+ const isFavorite = (info) => favoriteSet.has(info.symbol);
1925
1932
  const filter = (list) => list.filter((i3) => matchCategory(i3) && (!exchange || i3.exchange === exchange));
1926
1933
  const exchanges = w2(() => {
1927
1934
  const counts = /* @__PURE__ */ new Map();
@@ -1934,12 +1941,54 @@ function SymbolSearch({ ctx }) {
1934
1941
  const favoriteRows = filter(favorites).filter((f4) => !q2 || f4.name.toLowerCase().includes(q2) || f4.symbol.toLowerCase().includes(q2));
1935
1942
  const showRecent = view.kind === "all" && !q2 && recent.length > 0;
1936
1943
  const recentRows = showRecent ? filter(recent) : [];
1937
- const resultRows = view.kind === "favorites" ? favoriteRows : filter(results ?? []);
1944
+ const allResults = view.kind === "favorites" ? favoriteRows : filter(results ?? []);
1945
+ const preview = ctx.searchPreviewPerExchange;
1946
+ const more = /* @__PURE__ */ new Map();
1947
+ let resultRows = allResults;
1948
+ if (preview > 0 && view.kind === "all" && !exchange) {
1949
+ const seen = /* @__PURE__ */ new Map();
1950
+ const total = /* @__PURE__ */ new Map();
1951
+ for (const r3 of allResults) total.set(r3.exchange, (total.get(r3.exchange) ?? 0) + 1);
1952
+ resultRows = [];
1953
+ for (const r3 of allResults) {
1954
+ const n2 = (seen.get(r3.exchange) ?? 0) + 1;
1955
+ seen.set(r3.exchange, n2);
1956
+ if (n2 > preview) continue;
1957
+ resultRows.push(r3);
1958
+ if (n2 === preview && (total.get(r3.exchange) ?? 0) > preview) more.set(resultRows.length - 1, { exchange: r3.exchange, count: total.get(r3.exchange) });
1959
+ }
1960
+ }
1938
1961
  const rows = [...recentRows, ...resultRows];
1962
+ const shownRows = resultRows.length > limit ? resultRows.slice(0, limit) : resultRows;
1963
+ const exchangeLabel = (name) => ctx.searchExchanges.find((e3) => e3.name === name)?.label?.[locale] ?? name;
1939
1964
  A2(() => setActive(0), [query, category, exchange, view, results]);
1940
1965
  A2(() => {
1941
- listRef.current?.querySelector(`[data-index="${active}"]`)?.scrollIntoView({ block: "nearest" });
1966
+ setLimit(FIRST_ROWS);
1967
+ if (listRef.current) listRef.current.scrollTop = 0;
1968
+ }, [query, category, exchange, view]);
1969
+ const hasMore = resultRows.length > limit;
1970
+ A2(() => {
1971
+ const end = endRef.current;
1972
+ if (!hasMore || !end) return;
1973
+ const io = new IntersectionObserver((entries) => entries.some((e3) => e3.isIntersecting) && setLimit((l3) => l3 + MORE_ROWS), {
1974
+ root: listRef.current,
1975
+ rootMargin: "0px 0px 600px 0px"
1976
+ });
1977
+ io.observe(end);
1978
+ return () => io.disconnect();
1979
+ }, [hasMore, limit]);
1980
+ const scrollToActive = T2(false);
1981
+ A2(() => {
1982
+ if (active - recentRows.length >= limit - 1) {
1983
+ scrollToActive.current = true;
1984
+ setLimit(active - recentRows.length + MORE_ROWS);
1985
+ } else listRef.current?.querySelector(`[data-index="${active}"]`)?.scrollIntoView({ block: "nearest" });
1942
1986
  }, [active]);
1987
+ A2(() => {
1988
+ if (!scrollToActive.current) return;
1989
+ scrollToActive.current = false;
1990
+ listRef.current?.querySelector(`[data-index="${active}"]`)?.scrollIntoView({ block: "nearest" });
1991
+ }, [limit]);
1943
1992
  const close = () => ctx.setSearchOpen(false);
1944
1993
  const save = (key, next, set) => {
1945
1994
  set(next);
@@ -2029,7 +2078,8 @@ function SymbolSearch({ ctx }) {
2029
2078
  }
2030
2079
  )
2031
2080
  ]
2032
- }
2081
+ },
2082
+ (isRecent ? "recent:" : "") + info.symbol
2033
2083
  );
2034
2084
  };
2035
2085
  const sideItem = (label, selected, onClick, extra, logoUrl) => /* @__PURE__ */ a3("button", { class: "fnc-sr-side-item", "aria-pressed": selected, onClick, children: [
@@ -2085,7 +2135,14 @@ function SymbolSearch({ ctx }) {
2085
2135
  ] }),
2086
2136
  recentRows.map((info, i3) => row2(info, i3, true)),
2087
2137
  (recentRows.length > 0 || view.kind === "favorites") && /* @__PURE__ */ a3("div", { class: "fnc-sr-section", children: view.kind === "favorites" ? t3.favorites : t3.symbols }),
2088
- loading && !results && view.kind === "all" ? Array.from({ length: 6 }, () => /* @__PURE__ */ a3("div", { class: "fnc-sr-skeleton", "aria-hidden": "true" })) : resultRows.map((info, i3) => row2(info, recentRows.length + i3, false)),
2138
+ loading && !results && view.kind === "all" ? Array.from({ length: 6 }, () => /* @__PURE__ */ a3("div", { class: "fnc-sr-skeleton", "aria-hidden": "true" })) : shownRows.map((info, i3) => {
2139
+ const m3 = more.get(i3);
2140
+ return m3 ? /* @__PURE__ */ a3($, { children: [
2141
+ row2(info, recentRows.length + i3, false),
2142
+ /* @__PURE__ */ a3("button", { class: "fnc-sr-more", onClick: () => (setView({ kind: "all" }), setExchange(m3.exchange)), children: t3.showAllIn.replace("{exchange}", exchangeLabel(m3.exchange)).replace("{n}", m3.count.toLocaleString(locale === "ko" ? "ko-KR" : "en-US")) })
2143
+ ] }, info.symbol) : row2(info, recentRows.length + i3, false);
2144
+ }),
2145
+ hasMore && /* @__PURE__ */ a3("div", { ref: endRef, class: "fnc-sr-end", "aria-hidden": "true" }),
2089
2146
  !loading && rows.length === 0 && /* @__PURE__ */ a3("div", { class: "fnc-empty", children: view.kind === "favorites" && favorites.length === 0 ? t3.noFavorites : t3.noResults })
2090
2147
  ] })
2091
2148
  ] }),
@@ -3228,6 +3285,9 @@ var CSS = (
3228
3285
  .fnc-sr-side-item em { font-style: normal; font-size: 12px; color: var(--fnc-text-2); }
3229
3286
  .fnc-sr-side-logo { flex: none; width: 18px; height: 18px; border-radius: 5px; object-fit: cover; background: var(--fnc-surface); }
3230
3287
  .fnc-sr-spark-slot { display: inline-flex; flex: none; width: 72px; height: 24px; }
3288
+ .fnc-sr-more { display: block; width: calc(100% - 16px); margin: 2px 8px 8px; padding: 8px 12px; border-radius: var(--fnc-radius); background: var(--fnc-surface); color: var(--fnc-text-2); font-size: 13px; font-weight: 600; text-align: left; }
3289
+ .fnc-sr-more:hover { color: var(--fnc-text); }
3290
+ .fnc-sr-end { height: 1px; }
3231
3291
  .fnc-sr-side-item:hover { background: var(--fnc-surface); }
3232
3292
  .fnc-sr-side-item[aria-pressed='true'] { background: var(--fnc-surface); font-weight: 700; }
3233
3293
  .fnc-chip-token {
@@ -4255,6 +4315,7 @@ function createPane(shell, config) {
4255
4315
  intervalGroups: config.intervalGroups,
4256
4316
  searchCategories: config.searchCategories,
4257
4317
  searchExchanges: config.searchExchanges ?? [],
4318
+ searchPreviewPerExchange: config.searchPreviewPerExchange ?? 0,
4258
4319
  displayTimezone: config.displayTimezone,
4259
4320
  showLogo: config.symbolLogo,
4260
4321
  searchHotkey: config.searchHotkey,
@@ -4330,6 +4391,7 @@ function createTradingChart(container, options) {
4330
4391
  onFavoriteIntervalsChange,
4331
4392
  searchCategories = DEFAULT_SEARCH_CATEGORIES,
4332
4393
  searchExchanges = [],
4394
+ searchPreviewPerExchange = 0,
4333
4395
  storageKey = "funnest-charts",
4334
4396
  searchHotkey = true,
4335
4397
  symbolLogo = true,
@@ -4360,6 +4422,7 @@ function createTradingChart(container, options) {
4360
4422
  intervalGroups,
4361
4423
  searchCategories,
4362
4424
  searchExchanges,
4425
+ searchPreviewPerExchange,
4363
4426
  symbolLogo,
4364
4427
  searchHotkey,
4365
4428
  drawingToolbar,
@@ -4430,6 +4493,7 @@ function createMultiChart(container, options) {
4430
4493
  onFavoriteIntervalsChange,
4431
4494
  searchCategories = DEFAULT_SEARCH_CATEGORIES,
4432
4495
  searchExchanges = [],
4496
+ searchPreviewPerExchange = 0,
4433
4497
  storageKey = "funnest-charts",
4434
4498
  searchHotkey = true,
4435
4499
  symbolLogo = true,
@@ -4501,6 +4565,7 @@ function createMultiChart(container, options) {
4501
4565
  intervalGroups,
4502
4566
  searchCategories,
4503
4567
  searchExchanges,
4568
+ searchPreviewPerExchange,
4504
4569
  symbolLogo,
4505
4570
  searchHotkey,
4506
4571
  drawingToolbar,
package/dist/pane.d.ts CHANGED
@@ -12,6 +12,7 @@ export interface PaneConfig {
12
12
  intervalGroups: readonly IntervalGroup[];
13
13
  searchCategories: readonly SearchCategory[];
14
14
  searchExchanges?: readonly SearchExchange[];
15
+ searchPreviewPerExchange?: number;
15
16
  symbolLogo: boolean;
16
17
  drawingToolbar: boolean;
17
18
  attribution: boolean;
@@ -24,6 +24,11 @@ export interface TradingChartOptions extends Omit<ChartOptions, 'datafeed'> {
24
24
  searchCategories?: readonly SearchCategory[];
25
25
  /** Exchanges listed in the symbol search sidebar from the start, with logos (counts fill in as results come). */
26
26
  searchExchanges?: readonly SearchExchange[];
27
+ /**
28
+ * Symbol search: rows per exchange shown at first, each followed by "N개 모두 보기" when there
29
+ * are more (picking the exchange lists them all). Default 0: everything at once.
30
+ */
31
+ searchPreviewPerExchange?: number;
27
32
  /**
28
33
  * localStorage key prefix for UI preferences (favorite intervals, recent searches).
29
34
  * Default 'funnest-charts'. Pass null to keep them in memory only.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnest/charts-ui",
3
- "version": "0.6.14",
3
+ "version": "0.6.16",
4
4
  "description": "Pro trading UI (toolbar, drawing tools, sheets) for Funnest Charts. Framework-agnostic.",
5
5
  "keywords": [
6
6
  "chart",
@@ -42,7 +42,7 @@
42
42
  "NOTICE"
43
43
  ],
44
44
  "dependencies": {
45
- "@funnest/charts": "0.6.14"
45
+ "@funnest/charts": "0.6.16"
46
46
  },
47
47
  "devDependencies": {
48
48
  "preact": "^11.2.0"