@funnest/charts-ui 0.6.12 → 0.6.14
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 +10 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +70 -7
- package/dist/pane.d.ts +2 -1
- package/dist/trading-chart.d.ts +3 -1
- package/package.json +2 -2
package/dist/context.d.ts
CHANGED
|
@@ -60,6 +60,15 @@ export interface SearchCategory {
|
|
|
60
60
|
label: Record<Locale, string>;
|
|
61
61
|
match(info: SymbolInfo): boolean;
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* An exchange listed in the symbol search sidebar from the start (before any results), with its
|
|
65
|
+
* logo. `name` is the `SymbolInfo.exchange` its symbols carry; `label` what to show.
|
|
66
|
+
*/
|
|
67
|
+
export interface SearchExchange {
|
|
68
|
+
name: string;
|
|
69
|
+
label?: Record<Locale, string>;
|
|
70
|
+
logoUrl?: string;
|
|
71
|
+
}
|
|
63
72
|
/** Everything components need; created once per widget. */
|
|
64
73
|
export interface WidgetContext {
|
|
65
74
|
/** The widget's root element (theme variables live here). */
|
|
@@ -70,6 +79,7 @@ export interface WidgetContext {
|
|
|
70
79
|
storage: WidgetStorage;
|
|
71
80
|
intervalGroups: readonly IntervalGroup[];
|
|
72
81
|
searchCategories: readonly SearchCategory[];
|
|
82
|
+
searchExchanges: readonly SearchExchange[];
|
|
73
83
|
/** IANA zone used for the status-bar clock (KST for Korean users by default). */
|
|
74
84
|
displayTimezone: string;
|
|
75
85
|
showLogo: boolean;
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,6 @@ export { createMultiChart } from './multi-chart';
|
|
|
8
8
|
export type { MultiChart, MultiChartEvents, MultiChartOptions, MultiChartState, PaneSpec } from './multi-chart';
|
|
9
9
|
export { LAYOUT_GROUPS, LAYOUTS, MAX_PANES, paneCount } from './layouts';
|
|
10
10
|
export type { LayoutId, SplitNode, SplitSizes } from './layouts';
|
|
11
|
-
export type { ContextMenuItem, ContextMenuItems, IntervalGroup, LayoutSync, SearchCategory } from './context';
|
|
11
|
+
export type { ContextMenuItem, ContextMenuItems, IntervalGroup, LayoutSync, SearchCategory, SearchExchange } from './context';
|
|
12
12
|
export type { ColorScheme, TradingChart, TradingChartOptions, TradingChartSlots } from './trading-chart';
|
|
13
13
|
export { resolutionLabel } from './i18n';
|
package/dist/index.js
CHANGED
|
@@ -827,6 +827,7 @@ var EyeIcon = () => /* @__PURE__ */ a3(Icon, { size: 14, children: [
|
|
|
827
827
|
/* @__PURE__ */ a3("circle", { cx: "10", cy: "10", r: "2.3" })
|
|
828
828
|
] });
|
|
829
829
|
var EyeOffIcon = () => /* @__PURE__ */ a3(Icon, { size: 14, children: /* @__PURE__ */ a3("path", { d: "M3 3l14 14M8.2 5a8 8 0 0 1 1.8-.5c5 0 8 5.5 8 5.5a14 14 0 0 1-2.2 2.9M5.4 6.4A14 14 0 0 0 2 10s3 5.5 8 5.5a7.7 7.7 0 0 0 3.6-.9" }) });
|
|
830
|
+
var ChevronUpIcon = () => /* @__PURE__ */ a3(Icon, { size: 14, children: /* @__PURE__ */ a3("path", { d: "M5 12l5-5 5 5" }) });
|
|
830
831
|
var MagnetIcon = () => /* @__PURE__ */ a3(Icon, { children: [
|
|
831
832
|
/* @__PURE__ */ a3("path", { d: "M5 4h3v6.5a2 2 0 0 0 4 0V4h3v6.5a5 5 0 0 1-10 0z" }),
|
|
832
833
|
/* @__PURE__ */ a3("path", { d: "M5 7h3M12 7h3" })
|
|
@@ -838,7 +839,6 @@ var StarIcon = ({ filled = false }) => /* @__PURE__ */ a3(Icon, { size: 16, chil
|
|
|
838
839
|
fill: filled ? "currentColor" : "none"
|
|
839
840
|
}
|
|
840
841
|
) });
|
|
841
|
-
var CollapseLeftIcon = () => /* @__PURE__ */ a3(Icon, { size: 14, children: /* @__PURE__ */ a3("path", { d: "M11 5l-5 5 5 5M15 5l-5 5 5 5" }) });
|
|
842
842
|
var SettingsIcon = () => /* @__PURE__ */ a3(Icon, { children: [
|
|
843
843
|
/* @__PURE__ */ a3("path", { d: "M10 12.6a2.6 2.6 0 1 0 0-5.2 2.6 2.6 0 0 0 0 5.2z" }),
|
|
844
844
|
/* @__PURE__ */ a3("path", { d: "M16.2 12a1.3 1.3 0 0 0 .26 1.43l.05.05a1.6 1.6 0 1 1-2.26 2.26l-.05-.05a1.3 1.3 0 0 0-1.43-.26 1.3 1.3 0 0 0-.79 1.19v.14a1.6 1.6 0 0 1-3.2 0v-.07a1.3 1.3 0 0 0-.85-1.19 1.3 1.3 0 0 0-1.43.26l-.05.05a1.6 1.6 0 1 1-2.26-2.26l.05-.05A1.3 1.3 0 0 0 4.5 12a1.3 1.3 0 0 0-1.19-.79h-.14a1.6 1.6 0 0 1 0-3.2h.07a1.3 1.3 0 0 0 1.19-.85 1.3 1.3 0 0 0-.26-1.43l-.05-.05a1.6 1.6 0 1 1 2.26-2.26l.05.05a1.3 1.3 0 0 0 1.43.26h.06a1.3 1.3 0 0 0 .79-1.19v-.14a1.6 1.6 0 0 1 3.2 0v.07a1.3 1.3 0 0 0 .79 1.19 1.3 1.3 0 0 0 1.43-.26l.05-.05a1.6 1.6 0 1 1 2.26 2.26l-.05.05a1.3 1.3 0 0 0-.26 1.43v.06a1.3 1.3 0 0 0 1.19.79h.14a1.6 1.6 0 0 1 0 3.2h-.07a1.3 1.3 0 0 0-1.19.79z" })
|
|
@@ -1836,6 +1836,41 @@ function Highlight({ text, query }) {
|
|
|
1836
1836
|
parts.push(text.slice(i3));
|
|
1837
1837
|
return /* @__PURE__ */ a3($, { children: parts });
|
|
1838
1838
|
}
|
|
1839
|
+
var sparkCache = /* @__PURE__ */ new Map();
|
|
1840
|
+
var SPARK_TTL = 12e4;
|
|
1841
|
+
var SPARK_DWELL_MS = 300;
|
|
1842
|
+
function LazySparkline({ ctx, info, up }) {
|
|
1843
|
+
const ref = T2(null);
|
|
1844
|
+
const [values, setValues] = d2(info.sparkline ?? null);
|
|
1845
|
+
A2(() => {
|
|
1846
|
+
if (info.sparkline || !ctx.datafeed.getSparkline || !ref.current) return;
|
|
1847
|
+
let alive = true;
|
|
1848
|
+
const load = () => {
|
|
1849
|
+
let hit = sparkCache.get(info.symbol);
|
|
1850
|
+
if (!hit || Date.now() - hit.at > SPARK_TTL) {
|
|
1851
|
+
hit = { at: Date.now(), values: ctx.datafeed.getSparkline(info.symbol).catch(() => []) };
|
|
1852
|
+
sparkCache.set(info.symbol, hit);
|
|
1853
|
+
}
|
|
1854
|
+
void hit.values.then((v3) => alive && setValues(v3));
|
|
1855
|
+
};
|
|
1856
|
+
let timer;
|
|
1857
|
+
const io = new IntersectionObserver((entries) => {
|
|
1858
|
+
clearTimeout(timer);
|
|
1859
|
+
if (!entries.some((e3) => e3.isIntersecting)) return;
|
|
1860
|
+
timer = setTimeout(() => {
|
|
1861
|
+
io.disconnect();
|
|
1862
|
+
load();
|
|
1863
|
+
}, SPARK_DWELL_MS);
|
|
1864
|
+
});
|
|
1865
|
+
io.observe(ref.current);
|
|
1866
|
+
return () => {
|
|
1867
|
+
alive = false;
|
|
1868
|
+
clearTimeout(timer);
|
|
1869
|
+
io.disconnect();
|
|
1870
|
+
};
|
|
1871
|
+
}, [info.symbol]);
|
|
1872
|
+
return /* @__PURE__ */ a3("span", { ref, class: "fnc-sr-spark-slot", children: values && /* @__PURE__ */ a3(Sparkline, { values, up }) });
|
|
1873
|
+
}
|
|
1839
1874
|
function Sparkline({ values, up }) {
|
|
1840
1875
|
if (values.length < 2) return null;
|
|
1841
1876
|
const min = Math.min(...values);
|
|
@@ -1855,6 +1890,7 @@ function SymbolSearch({ ctx }) {
|
|
|
1855
1890
|
const [exchange, setExchange] = d2(null);
|
|
1856
1891
|
const [results, setResults] = d2(null);
|
|
1857
1892
|
const [loading, setLoading] = d2(true);
|
|
1893
|
+
const [pending, setPending] = d2(true);
|
|
1858
1894
|
const [recent, setRecent] = d2(() => ctx.storage.get(RECENT_KEY2, []));
|
|
1859
1895
|
const [favorites, setFavorites] = d2(() => ctx.storage.get(FAVORITE_KEY, []));
|
|
1860
1896
|
const [active, setActive] = d2(0);
|
|
@@ -1864,8 +1900,19 @@ function SymbolSearch({ ctx }) {
|
|
|
1864
1900
|
A2(() => {
|
|
1865
1901
|
let alive = true;
|
|
1866
1902
|
setLoading(true);
|
|
1903
|
+
setPending(true);
|
|
1867
1904
|
const id = setTimeout(() => {
|
|
1868
|
-
|
|
1905
|
+
const feed = ctx.datafeed;
|
|
1906
|
+
const search = feed.searchSymbolsProgressive ? feed.searchSymbolsProgressive(query.trim(), (partial) => {
|
|
1907
|
+
if (!alive) return;
|
|
1908
|
+
setResults(partial);
|
|
1909
|
+
setLoading(false);
|
|
1910
|
+
}) : feed.searchSymbols?.(query.trim());
|
|
1911
|
+
search?.then((items) => alive && setResults(items)).catch(() => alive && setResults([])).finally(() => {
|
|
1912
|
+
if (!alive) return;
|
|
1913
|
+
setLoading(false);
|
|
1914
|
+
setPending(false);
|
|
1915
|
+
});
|
|
1869
1916
|
}, DEBOUNCE_MS);
|
|
1870
1917
|
return () => {
|
|
1871
1918
|
alive = false;
|
|
@@ -1879,7 +1926,9 @@ function SymbolSearch({ ctx }) {
|
|
|
1879
1926
|
const exchanges = w2(() => {
|
|
1880
1927
|
const counts = /* @__PURE__ */ new Map();
|
|
1881
1928
|
for (const r3 of results ?? []) if (matchCategory(r3)) counts.set(r3.exchange, (counts.get(r3.exchange) ?? 0) + 1);
|
|
1882
|
-
|
|
1929
|
+
const declared = ctx.searchExchanges.map((e3) => ({ ...e3, count: counts.get(e3.name) }));
|
|
1930
|
+
const others = [...counts.entries()].filter(([name]) => !ctx.searchExchanges.some((e3) => e3.name === name)).sort((a4, b) => b[1] - a4[1]).map(([name, count]) => ({ name, label: void 0, logoUrl: void 0, count }));
|
|
1931
|
+
return [...declared, ...others];
|
|
1883
1932
|
}, [results, category]);
|
|
1884
1933
|
const q2 = query.trim().toLowerCase();
|
|
1885
1934
|
const favoriteRows = filter(favorites).filter((f4) => !q2 || f4.name.toLowerCase().includes(q2) || f4.symbol.toLowerCase().includes(q2));
|
|
@@ -1957,7 +2006,7 @@ function SymbolSearch({ ctx }) {
|
|
|
1957
2006
|
cat && ` \xB7 ${cat.label[locale]}`
|
|
1958
2007
|
] })
|
|
1959
2008
|
] }),
|
|
1960
|
-
info.sparkline && /* @__PURE__ */ a3(
|
|
2009
|
+
(info.sparkline || ctx.datafeed.getSparkline) && /* @__PURE__ */ a3(LazySparkline, { ctx, info, up }, info.symbol),
|
|
1961
2010
|
info.price !== void 0 && /* @__PURE__ */ a3("span", { class: "fnc-sr-quote", children: [
|
|
1962
2011
|
/* @__PURE__ */ a3("b", { children: formatPrice(info.price, info.pricePrecision) }),
|
|
1963
2012
|
info.changePercent !== void 0 && /* @__PURE__ */ a3("span", { class: trend, children: [
|
|
@@ -1983,7 +2032,8 @@ function SymbolSearch({ ctx }) {
|
|
|
1983
2032
|
}
|
|
1984
2033
|
);
|
|
1985
2034
|
};
|
|
1986
|
-
const sideItem = (label, selected, onClick, extra) => /* @__PURE__ */ a3("button", { class: "fnc-sr-side-item", "aria-pressed": selected, onClick, children: [
|
|
2035
|
+
const sideItem = (label, selected, onClick, extra, logoUrl) => /* @__PURE__ */ a3("button", { class: "fnc-sr-side-item", "aria-pressed": selected, onClick, children: [
|
|
2036
|
+
logoUrl !== void 0 && (logoUrl ? /* @__PURE__ */ a3("img", { class: "fnc-sr-side-logo", src: logoUrl, alt: "", "aria-hidden": "true" }) : /* @__PURE__ */ a3("i", { class: "fnc-sr-side-logo", "aria-hidden": "true" })),
|
|
1987
2037
|
/* @__PURE__ */ a3("span", { children: label }),
|
|
1988
2038
|
extra
|
|
1989
2039
|
] });
|
|
@@ -2019,7 +2069,13 @@ function SymbolSearch({ ctx }) {
|
|
|
2019
2069
|
sideItem(t3.favorites, view.kind === "favorites", () => (setView({ kind: "favorites" }), setExchange(null)), /* @__PURE__ */ a3("em", { children: favorites.length || "" })),
|
|
2020
2070
|
exchanges.length > 0 && /* @__PURE__ */ a3("div", { class: "fnc-sr-side-title", children: t3.exchanges }),
|
|
2021
2071
|
exchanges.map(
|
|
2022
|
-
(
|
|
2072
|
+
(x2) => sideItem(
|
|
2073
|
+
x2.label?.[locale] ?? x2.name,
|
|
2074
|
+
exchange === x2.name,
|
|
2075
|
+
() => (setView({ kind: "all" }), setExchange(exchange === x2.name ? null : x2.name)),
|
|
2076
|
+
/* @__PURE__ */ a3("em", { children: x2.count ?? (pending ? "" : 0) }),
|
|
2077
|
+
ctx.searchExchanges.length ? x2.logoUrl ?? "" : void 0
|
|
2078
|
+
)
|
|
2023
2079
|
)
|
|
2024
2080
|
] }),
|
|
2025
2081
|
/* @__PURE__ */ a3("div", { class: "fnc-sr-list fnc-scroll", id: "fnc-search-results", role: "listbox", ref: listRef, children: [
|
|
@@ -2607,7 +2663,7 @@ function Legend({ ctx }) {
|
|
|
2607
2663
|
)
|
|
2608
2664
|
] })),
|
|
2609
2665
|
!collapsed && overlayItems.map((item) => /* @__PURE__ */ a3(IndicatorItem, { ctx, item })),
|
|
2610
|
-
overlayItems.length > 0 && (collapsed ? /* @__PURE__ */ a3("button", { class: "fnc-ind-handle", "aria-expanded": "false", "aria-label": t3.expand, "data-tip": t3.expand, onClick: () => setCollapsed(false), children: /* @__PURE__ */ a3("span", {}) }) : /* @__PURE__ */ a3("button", { class: "fnc-ind-collapse", "aria-expanded": "true", "aria-label": t3.collapse, "data-tip": t3.collapse, onClick: () => setCollapsed(true), children: /* @__PURE__ */ a3(
|
|
2666
|
+
overlayItems.length > 0 && (collapsed ? /* @__PURE__ */ a3("button", { class: "fnc-ind-handle", "aria-expanded": "false", "aria-label": t3.expand, "data-tip": t3.expand, onClick: () => setCollapsed(false), children: /* @__PURE__ */ a3("span", {}) }) : /* @__PURE__ */ a3("button", { class: "fnc-ind-collapse", "aria-expanded": "true", "aria-label": t3.collapse, "data-tip": t3.collapse, onClick: () => setCollapsed(true), children: /* @__PURE__ */ a3(ChevronUpIcon, {}) }))
|
|
2611
2667
|
] }),
|
|
2612
2668
|
paneLegends.map(
|
|
2613
2669
|
({ pane, item }) => item && /* @__PURE__ */ a3("div", { class: "fnc-legend fnc-pane-legend", style: { top: `${pane.top + 4}px` }, children: /* @__PURE__ */ a3(IndicatorItem, { ctx, item }) })
|
|
@@ -3170,6 +3226,8 @@ var CSS = (
|
|
|
3170
3226
|
}
|
|
3171
3227
|
.fnc-sr-side-item span { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
3172
3228
|
.fnc-sr-side-item em { font-style: normal; font-size: 12px; color: var(--fnc-text-2); }
|
|
3229
|
+
.fnc-sr-side-logo { flex: none; width: 18px; height: 18px; border-radius: 5px; object-fit: cover; background: var(--fnc-surface); }
|
|
3230
|
+
.fnc-sr-spark-slot { display: inline-flex; flex: none; width: 72px; height: 24px; }
|
|
3173
3231
|
.fnc-sr-side-item:hover { background: var(--fnc-surface); }
|
|
3174
3232
|
.fnc-sr-side-item[aria-pressed='true'] { background: var(--fnc-surface); font-weight: 700; }
|
|
3175
3233
|
.fnc-chip-token {
|
|
@@ -4196,6 +4254,7 @@ function createPane(shell, config) {
|
|
|
4196
4254
|
storage: shell.storage,
|
|
4197
4255
|
intervalGroups: config.intervalGroups,
|
|
4198
4256
|
searchCategories: config.searchCategories,
|
|
4257
|
+
searchExchanges: config.searchExchanges ?? [],
|
|
4199
4258
|
displayTimezone: config.displayTimezone,
|
|
4200
4259
|
showLogo: config.symbolLogo,
|
|
4201
4260
|
searchHotkey: config.searchHotkey,
|
|
@@ -4270,6 +4329,7 @@ function createTradingChart(container, options) {
|
|
|
4270
4329
|
favoriteIntervals,
|
|
4271
4330
|
onFavoriteIntervalsChange,
|
|
4272
4331
|
searchCategories = DEFAULT_SEARCH_CATEGORIES,
|
|
4332
|
+
searchExchanges = [],
|
|
4273
4333
|
storageKey = "funnest-charts",
|
|
4274
4334
|
searchHotkey = true,
|
|
4275
4335
|
symbolLogo = true,
|
|
@@ -4299,6 +4359,7 @@ function createTradingChart(container, options) {
|
|
|
4299
4359
|
chartType,
|
|
4300
4360
|
intervalGroups,
|
|
4301
4361
|
searchCategories,
|
|
4362
|
+
searchExchanges,
|
|
4302
4363
|
symbolLogo,
|
|
4303
4364
|
searchHotkey,
|
|
4304
4365
|
drawingToolbar,
|
|
@@ -4368,6 +4429,7 @@ function createMultiChart(container, options) {
|
|
|
4368
4429
|
favoriteIntervals,
|
|
4369
4430
|
onFavoriteIntervalsChange,
|
|
4370
4431
|
searchCategories = DEFAULT_SEARCH_CATEGORIES,
|
|
4432
|
+
searchExchanges = [],
|
|
4371
4433
|
storageKey = "funnest-charts",
|
|
4372
4434
|
searchHotkey = true,
|
|
4373
4435
|
symbolLogo = true,
|
|
@@ -4438,6 +4500,7 @@ function createMultiChart(container, options) {
|
|
|
4438
4500
|
chartType,
|
|
4439
4501
|
intervalGroups,
|
|
4440
4502
|
searchCategories,
|
|
4503
|
+
searchExchanges,
|
|
4441
4504
|
symbolLogo,
|
|
4442
4505
|
searchHotkey,
|
|
4443
4506
|
drawingToolbar,
|
package/dist/pane.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** @jsxImportSource preact */
|
|
2
2
|
import { type Chart, type ChartOptions, type ChartType, type Datafeed, type Resolution } from '@funnest/charts';
|
|
3
|
-
import type { ContextMenuItems, IntervalGroup, MultiControls, SearchCategory, UiState, WidgetContext } from './context';
|
|
3
|
+
import type { ContextMenuItems, IntervalGroup, MultiControls, SearchCategory, SearchExchange, UiState, WidgetContext } from './context';
|
|
4
4
|
import { type Shell } from './shell';
|
|
5
5
|
import { Store } from './store';
|
|
6
6
|
export interface PaneConfig {
|
|
@@ -11,6 +11,7 @@ export interface PaneConfig {
|
|
|
11
11
|
chartType: ChartType;
|
|
12
12
|
intervalGroups: readonly IntervalGroup[];
|
|
13
13
|
searchCategories: readonly SearchCategory[];
|
|
14
|
+
searchExchanges?: readonly SearchExchange[];
|
|
14
15
|
symbolLogo: boolean;
|
|
15
16
|
drawingToolbar: boolean;
|
|
16
17
|
attribution: boolean;
|
package/dist/trading-chart.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** @jsxImportSource preact */
|
|
2
2
|
import { type Chart, type ChartOptions, type ChartType, type Datafeed, type Locale, type ChartState, type Resolution, type ThemeTokens } from '@funnest/charts';
|
|
3
|
-
import type { ContextMenuItems, IntervalGroup, SearchCategory } from './context';
|
|
3
|
+
import type { ContextMenuItems, IntervalGroup, SearchCategory, SearchExchange } from './context';
|
|
4
4
|
import { type ColorScheme, type TradingChartSlots } from './shell';
|
|
5
5
|
export type { ColorScheme, TradingChartSlots } from './shell';
|
|
6
6
|
/** Interval menu: 분 | 시간 | 일. '12M' is shown as 1년. */
|
|
@@ -22,6 +22,8 @@ export interface TradingChartOptions extends Omit<ChartOptions, 'datafeed'> {
|
|
|
22
22
|
onFavoriteIntervalsChange?: (intervals: Resolution[]) => void;
|
|
23
23
|
/** Symbol search tabs. */
|
|
24
24
|
searchCategories?: readonly SearchCategory[];
|
|
25
|
+
/** Exchanges listed in the symbol search sidebar from the start, with logos (counts fill in as results come). */
|
|
26
|
+
searchExchanges?: readonly SearchExchange[];
|
|
25
27
|
/**
|
|
26
28
|
* localStorage key prefix for UI preferences (favorite intervals, recent searches).
|
|
27
29
|
* 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.
|
|
3
|
+
"version": "0.6.14",
|
|
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.
|
|
45
|
+
"@funnest/charts": "0.6.14"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"preact": "^11.2.0"
|