rune-lab 0.3.0 → 0.4.0
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/README.md +1 -1
- package/dist/core/design-tokens/props.d.ts +52 -0
- package/dist/core/design-tokens/props.d.ts.map +1 -0
- package/dist/core/design-tokens/props.js +34 -0
- package/dist/core/exchange-rate/strategies.d.ts +52 -0
- package/dist/core/exchange-rate/strategies.d.ts.map +1 -0
- package/dist/core/exchange-rate/strategies.js +72 -0
- package/dist/core/index.d.ts +8 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +8 -3
- package/dist/core/internal/message-resolver.d.ts +1 -1
- package/dist/core/internal/message-resolver.d.ts.map +1 -1
- package/dist/core/layout/types.d.ts +60 -0
- package/dist/core/layout/types.d.ts.map +1 -0
- package/dist/core/layout/types.js +4 -0
- package/dist/core/money/index.d.ts +1 -1
- package/dist/core/money/index.d.ts.map +1 -1
- package/dist/core/money/index.js +1 -1
- package/dist/core/money/money-primitive.d.ts +101 -0
- package/dist/core/money/money-primitive.d.ts.map +1 -0
- package/dist/core/money/money-primitive.js +161 -0
- package/dist/core/money/money.d.ts +74 -2
- package/dist/core/money/money.d.ts.map +1 -1
- package/dist/core/money/money.js +120 -2
- package/dist/core/shortcuts/types.d.ts +60 -0
- package/dist/core/shortcuts/types.d.ts.map +1 -0
- package/dist/core/shortcuts/types.js +4 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +6 -4
- package/dist/state/api.svelte.js +2 -2
- package/dist/state/app.svelte.js +1 -1
- package/dist/state/auth/index.d.ts +2 -2
- package/dist/state/auth/index.js +1 -1
- package/dist/state/auth/session.svelte.d.ts +1 -1
- package/dist/state/auth/session.svelte.js +7 -5
- package/dist/state/auth/types.d.ts +1 -1
- package/dist/state/cart.svelte.d.ts +1 -1
- package/dist/state/cart.svelte.js +1 -1
- package/dist/state/commands.svelte.d.ts +7 -7
- package/dist/state/commands.svelte.js +1 -1
- package/dist/state/composables/useMoney.d.ts +19 -3
- package/dist/state/composables/useMoney.js +70 -6
- package/dist/state/composables/useMoneyFilter.d.ts +20 -0
- package/dist/state/composables/useMoneyFilter.js +81 -0
- package/dist/state/composables/usePersistence.d.ts +1 -1
- package/dist/state/composables/usePersistence.js +1 -1
- package/dist/state/composables/useRuneLab.d.ts +1 -1
- package/dist/state/composables/useRuneLab.js +2 -2
- package/dist/state/composables/useShortcuts.d.ts +33 -0
- package/dist/state/composables/useShortcuts.js +75 -0
- package/dist/state/context.d.ts +1 -0
- package/dist/state/context.js +1 -0
- package/dist/state/createConfigStore.svelte.d.ts +4 -31
- package/dist/state/createConfigStore.svelte.js +62 -51
- package/dist/state/currency.svelte.d.ts +13 -9
- package/dist/state/currency.svelte.js +26 -10
- package/dist/state/currency.test.d.ts +1 -0
- package/dist/state/currency.test.js +35 -0
- package/dist/state/exchange-rate.svelte.d.ts +43 -0
- package/dist/state/exchange-rate.svelte.js +145 -0
- package/dist/state/exchange-rate.test.d.ts +1 -0
- package/dist/state/exchange-rate.test.js +75 -0
- package/dist/state/index.d.ts +26 -19
- package/dist/state/index.js +25 -18
- package/dist/state/language.svelte.d.ts +3 -10
- package/dist/state/language.svelte.js +4 -5
- package/dist/state/layout.svelte.d.ts +1 -1
- package/dist/state/layout.svelte.js +4 -4
- package/dist/state/persistence/drivers.d.ts +1 -1
- package/dist/state/persistence/drivers.js +9 -7
- package/dist/state/persistence/drivers.test.d.ts +1 -0
- package/dist/state/persistence/drivers.test.js +79 -0
- package/dist/state/persistence/provider.d.ts +23 -0
- package/dist/state/persistence/provider.js +43 -0
- package/dist/state/persistence/provider.test.d.ts +1 -0
- package/dist/state/persistence/provider.test.js +51 -0
- package/dist/state/registry/index.d.ts +44 -0
- package/dist/state/registry/index.js +58 -0
- package/dist/state/registry/registry.test.d.ts +1 -0
- package/dist/state/registry/registry.test.js +112 -0
- package/dist/state/registry/types.d.ts +20 -0
- package/dist/state/registry/types.js +3 -0
- package/dist/state/shortcuts.svelte.js +4 -4
- package/dist/state/theme.svelte.d.ts +3 -10
- package/dist/state/theme.svelte.js +8 -8
- package/dist/state/toast-bridge.d.ts +1 -1
- package/dist/state/toast.svelte.js +1 -1
- package/dist/ui/components/ApiMonitor.svelte +2 -2
- package/dist/ui/components/Icon.svelte +1 -1
- package/dist/ui/components/RuneProvider.svelte +28 -8
- package/dist/ui/components/RuneProvider.svelte.d.ts +12 -5
- package/dist/ui/components/Toaster.svelte +1 -1
- package/dist/ui/components/money/MoneyDisplay.svelte +91 -18
- package/dist/ui/components/money/MoneyDisplay.svelte.d.ts +15 -3
- package/dist/ui/components/money/MoneyDisplay.svelte.test.d.ts +1 -1
- package/dist/ui/components/money/MoneyDisplay.svelte.test.js +45 -2
- package/dist/ui/components/money/MoneyInput.svelte +123 -42
- package/dist/ui/components/money/MoneyInput.svelte.d.ts +14 -5
- package/dist/ui/features/command-palette/CommandPalette.svelte +3 -3
- package/dist/ui/features/config/APP_CONFIGURATIONS.d.ts +29 -0
- package/dist/ui/features/config/APP_CONFIGURATIONS.js +38 -0
- package/dist/ui/features/config/CurrencySelector.svelte +10 -36
- package/dist/ui/features/config/LanguageSelector.svelte +10 -33
- package/dist/ui/features/config/ResourceSelector.svelte +92 -0
- package/dist/ui/features/config/ResourceSelector.svelte.d.ts +25 -0
- package/dist/ui/features/config/ThemeSelector.svelte +11 -34
- package/dist/ui/features/shortcuts/ShortcutBinder.svelte +17 -0
- package/dist/ui/features/shortcuts/ShortcutBinder.svelte.d.ts +7 -0
- package/dist/ui/features/shortcuts/ShortcutPalette.svelte +3 -3
- package/dist/ui/index.d.ts +5 -1
- package/dist/ui/index.js +8 -3
- package/dist/ui/layout/ConnectedNavigationPanel.svelte +7 -8
- package/dist/ui/layout/ConnectedNavigationPanel.svelte.d.ts +1 -1
- package/dist/ui/layout/ConnectedWorkspaceStrip.svelte +5 -3
- package/dist/ui/layout/ConnectedWorkspaceStrip.svelte.d.ts +1 -1
- package/dist/ui/layout/NavigationPanel.svelte +1 -1
- package/dist/ui/layout/NavigationPanel.svelte.d.ts +1 -1
- package/dist/ui/layout/WorkspaceLayout.svelte +9 -1
- package/dist/ui/layout/WorkspaceLayout.svelte.d.ts +7 -0
- package/dist/ui/layout/WorkspaceStrip.svelte +1 -1
- package/dist/ui/layout/WorkspaceStrip.svelte.d.ts +1 -1
- package/dist/ui/layout/connection-factory.d.ts +50 -0
- package/dist/ui/layout/connection-factory.js +58 -0
- package/dist/ui/layout/index.d.ts +2 -2
- package/dist/ui/layout/index.js +1 -1
- package/dist/ui/paraglide/README.md +53 -0
- package/dist/ui/paraglide/runtime.d.ts +105 -124
- package/dist/ui/paraglide/runtime.js +162 -127
- package/dist/ui/paraglide/server.d.ts +6 -17
- package/dist/ui/paraglide/server.js +11 -20
- package/dist/ui/primitives/DatePicker.svelte +1 -1
- package/package.json +8 -8
- package/dist/state/daisyui.d.ts +0 -4
|
@@ -1,69 +1,142 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { getLanguageStore, getCurrencyStore } from "
|
|
3
|
-
import {
|
|
2
|
+
import { getLanguageStore, getCurrencyStore } from "@internal/state";
|
|
3
|
+
import {
|
|
4
|
+
formatAmount,
|
|
5
|
+
toMinorUnit,
|
|
6
|
+
type ISO4217Code,
|
|
7
|
+
MoneyPrimitive,
|
|
8
|
+
} from "@internal/core";
|
|
9
|
+
import { DEV } from "esm-env";
|
|
4
10
|
|
|
5
11
|
let {
|
|
6
|
-
amount,
|
|
7
|
-
|
|
12
|
+
amount: amountProp,
|
|
13
|
+
money,
|
|
14
|
+
unit = "minor",
|
|
8
15
|
fallback = "—",
|
|
9
16
|
currency,
|
|
17
|
+
sourceCurrency,
|
|
18
|
+
showSourceCurrency = false,
|
|
19
|
+
noRatesFallback,
|
|
10
20
|
locale,
|
|
11
21
|
compact = false,
|
|
12
22
|
} = $props<{
|
|
13
23
|
/** Amount in minor or major units (see unit prop) */
|
|
14
|
-
amount
|
|
15
|
-
/**
|
|
24
|
+
amount?: number | null | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* MoneyPrimitive instance. When provided, amount, currency, and unit
|
|
27
|
+
* are derived from the primitive. Takes precedence over raw amount.
|
|
28
|
+
*/
|
|
29
|
+
money?: MoneyPrimitive;
|
|
30
|
+
/**
|
|
16
31
|
* Whether the amount is in 'major' (e.g., pesos) or 'minor' (e.g., centavos) units.
|
|
32
|
+
* @deprecated Use `money` prop with MoneyPrimitive instead.
|
|
17
33
|
* Defaults to 'minor' for backward compatibility.
|
|
18
34
|
*/
|
|
19
|
-
unit?:
|
|
35
|
+
unit?: "major" | "minor";
|
|
20
36
|
/** String to display if amount is null, undefined, or NaN. Defaults to "—" */
|
|
21
37
|
fallback?: string;
|
|
22
38
|
/** Override currency code (defaults to CurrencyStore.current) */
|
|
23
39
|
currency?: ISO4217Code | string;
|
|
40
|
+
/** The currency the amount is stored in (e.g. MXN) */
|
|
41
|
+
sourceCurrency?: ISO4217Code | string;
|
|
42
|
+
/** Show the original currency as a label */
|
|
43
|
+
showSourceCurrency?: boolean;
|
|
44
|
+
/** Fallback text if conversion is needed but rates are missing */
|
|
45
|
+
noRatesFallback?: string;
|
|
24
46
|
/** Override locale (defaults to LanguageStore.current) */
|
|
25
47
|
locale?: string;
|
|
26
48
|
/** Use compact notation (e.g., $1.2M) */
|
|
27
49
|
compact?: boolean;
|
|
28
50
|
}>();
|
|
29
51
|
|
|
52
|
+
// Dev warning for deprecated usage
|
|
53
|
+
$effect(() => {
|
|
54
|
+
if (DEV && money && unit !== "minor") {
|
|
55
|
+
console.warn(
|
|
56
|
+
"[MoneyDisplay] The `unit` prop is deprecated when using `money: MoneyPrimitive`. " +
|
|
57
|
+
"The unit is derived from the MoneyPrimitive instance.",
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Derive effective values from MoneyPrimitive or legacy props
|
|
63
|
+
const amount = $derived(money ? money.minor : amountProp);
|
|
64
|
+
const effectiveSourceCurrency = $derived(
|
|
65
|
+
money ? money.currencyCode : sourceCurrency,
|
|
66
|
+
);
|
|
67
|
+
|
|
30
68
|
const currencyStore = getCurrencyStore();
|
|
31
69
|
const languageStore = getLanguageStore();
|
|
32
70
|
|
|
33
|
-
const
|
|
71
|
+
const resolvedDisplayCurrency = $derived(
|
|
34
72
|
currency ?? String(currencyStore.current),
|
|
35
73
|
);
|
|
36
74
|
|
|
75
|
+
const resolvedSourceCurrency = $derived(
|
|
76
|
+
effectiveSourceCurrency ?? resolvedDisplayCurrency,
|
|
77
|
+
);
|
|
78
|
+
|
|
37
79
|
const resolvedLocale = $derived(
|
|
38
80
|
locale ?? (String(languageStore.current) || "en"),
|
|
39
81
|
);
|
|
40
82
|
|
|
41
|
-
const currencyMeta = $derived(currencyStore.get(
|
|
83
|
+
const currencyMeta = $derived(currencyStore.get(resolvedDisplayCurrency));
|
|
42
84
|
const decimals = $derived(currencyMeta?.decimals ?? 2);
|
|
43
85
|
|
|
44
86
|
const formatted = $derived.by(() => {
|
|
45
87
|
// If amount is null/undefined/NaN, use fallback
|
|
46
|
-
if (
|
|
88
|
+
if (
|
|
89
|
+
amount === null ||
|
|
90
|
+
amount === undefined ||
|
|
91
|
+
(typeof amount === "number" && isNaN(amount))
|
|
92
|
+
) {
|
|
47
93
|
return fallback;
|
|
48
94
|
}
|
|
49
95
|
|
|
50
|
-
// Convert to minor units if necessary
|
|
51
|
-
const minorAmount =
|
|
52
|
-
|
|
53
|
-
|
|
96
|
+
// Convert to minor units if necessary (in source currency)
|
|
97
|
+
const minorAmount =
|
|
98
|
+
unit === "major"
|
|
99
|
+
? toMinorUnit(Number(amount), resolvedSourceCurrency)
|
|
100
|
+
: amount;
|
|
101
|
+
|
|
102
|
+
// Conversion logic
|
|
103
|
+
let displayAmount = Number(minorAmount);
|
|
104
|
+
let displayCurrency = resolvedDisplayCurrency;
|
|
105
|
+
|
|
106
|
+
if (resolvedSourceCurrency !== resolvedDisplayCurrency) {
|
|
107
|
+
if (currencyStore.canConvert) {
|
|
108
|
+
displayAmount = currencyStore.convertAmount(
|
|
109
|
+
Number(minorAmount),
|
|
110
|
+
resolvedSourceCurrency,
|
|
111
|
+
resolvedDisplayCurrency,
|
|
112
|
+
);
|
|
113
|
+
} else if (noRatesFallback) {
|
|
114
|
+
return noRatesFallback;
|
|
115
|
+
} else {
|
|
116
|
+
// Fallback to source currency display if rates missing
|
|
117
|
+
displayCurrency = resolvedSourceCurrency;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
54
120
|
|
|
55
121
|
if (compact) {
|
|
56
|
-
const
|
|
122
|
+
const displayDecimals =
|
|
123
|
+
currencyStore.get(displayCurrency)?.decimals ?? 2;
|
|
124
|
+
const majorUnits = displayAmount / Math.pow(10, displayDecimals);
|
|
57
125
|
return new Intl.NumberFormat(resolvedLocale, {
|
|
58
126
|
style: "currency",
|
|
59
|
-
currency:
|
|
127
|
+
currency: displayCurrency,
|
|
60
128
|
notation: "compact",
|
|
61
129
|
maximumFractionDigits: 1,
|
|
62
130
|
}).format(majorUnits);
|
|
63
131
|
}
|
|
64
132
|
|
|
65
|
-
return formatAmount(
|
|
133
|
+
return formatAmount(displayAmount, displayCurrency, resolvedLocale);
|
|
66
134
|
});
|
|
67
135
|
</script>
|
|
68
136
|
|
|
69
|
-
<data value={amount} class="rl-money-display tabular-nums">
|
|
137
|
+
<data value={amount} class="rl-money-display tabular-nums">
|
|
138
|
+
{formatted}
|
|
139
|
+
{#if showSourceCurrency && sourceCurrency && sourceCurrency !== resolvedDisplayCurrency}
|
|
140
|
+
<small class="opacity-50 ml-1">({sourceCurrency})</small>
|
|
141
|
+
{/if}
|
|
142
|
+
</data>
|
|
@@ -1,16 +1,28 @@
|
|
|
1
|
-
import { type ISO4217Code } from "
|
|
1
|
+
import { type ISO4217Code, MoneyPrimitive } from "@internal/core";
|
|
2
2
|
type $$ComponentProps = {
|
|
3
3
|
/** Amount in minor or major units (see unit prop) */
|
|
4
|
-
amount
|
|
4
|
+
amount?: number | null | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* MoneyPrimitive instance. When provided, amount, currency, and unit
|
|
7
|
+
* are derived from the primitive. Takes precedence over raw amount.
|
|
8
|
+
*/
|
|
9
|
+
money?: MoneyPrimitive;
|
|
5
10
|
/**
|
|
6
11
|
* Whether the amount is in 'major' (e.g., pesos) or 'minor' (e.g., centavos) units.
|
|
12
|
+
* @deprecated Use `money` prop with MoneyPrimitive instead.
|
|
7
13
|
* Defaults to 'minor' for backward compatibility.
|
|
8
14
|
*/
|
|
9
|
-
unit?:
|
|
15
|
+
unit?: "major" | "minor";
|
|
10
16
|
/** String to display if amount is null, undefined, or NaN. Defaults to "—" */
|
|
11
17
|
fallback?: string;
|
|
12
18
|
/** Override currency code (defaults to CurrencyStore.current) */
|
|
13
19
|
currency?: ISO4217Code | string;
|
|
20
|
+
/** The currency the amount is stored in (e.g. MXN) */
|
|
21
|
+
sourceCurrency?: ISO4217Code | string;
|
|
22
|
+
/** Show the original currency as a label */
|
|
23
|
+
showSourceCurrency?: boolean;
|
|
24
|
+
/** Fallback text if conversion is needed but rates are missing */
|
|
25
|
+
noRatesFallback?: string;
|
|
14
26
|
/** Override locale (defaults to LanguageStore.current) */
|
|
15
27
|
locale?: string;
|
|
16
28
|
/** Use compact notation (e.g., $1.2M) */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
import "@testing-library/jest-dom/vitest";
|
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
import { render, screen } from "@testing-library/svelte";
|
|
2
|
+
import "@testing-library/jest-dom/vitest";
|
|
2
3
|
import { describe, expect, it } from "vitest";
|
|
3
4
|
import MoneyDisplay from "./MoneyDisplay.svelte";
|
|
4
|
-
import { RUNE_LAB_CONTEXT } from "
|
|
5
|
+
import { RUNE_LAB_CONTEXT } from "@internal/state";
|
|
5
6
|
// Mock stores for context
|
|
6
7
|
const mockCurrencyStore = {
|
|
7
8
|
current: "USD",
|
|
8
|
-
get: (code) =>
|
|
9
|
+
get: (code) => {
|
|
10
|
+
if (code === "JPY")
|
|
11
|
+
return { symbol: "¥", decimals: 0 };
|
|
12
|
+
if (code === "MXN")
|
|
13
|
+
return { symbol: "$", decimals: 2 };
|
|
14
|
+
return { symbol: "$", decimals: 2 };
|
|
15
|
+
},
|
|
16
|
+
canConvert: true,
|
|
17
|
+
convertAmount: (amount, from, to) => {
|
|
18
|
+
if (from === "USD" && to === "MXN")
|
|
19
|
+
return amount * 20;
|
|
20
|
+
if (from === "MXN" && to === "USD")
|
|
21
|
+
return amount / 20;
|
|
22
|
+
return amount;
|
|
23
|
+
}
|
|
9
24
|
};
|
|
10
25
|
const mockLanguageStore = {
|
|
11
26
|
current: "en-US",
|
|
@@ -71,4 +86,32 @@ describe("MoneyDisplay.svelte", () => {
|
|
|
71
86
|
});
|
|
72
87
|
expect(screen.getByText(/\$1\.2M/)).toBeInTheDocument();
|
|
73
88
|
});
|
|
89
|
+
it("converts from sourceCurrency to current display currency", () => {
|
|
90
|
+
// 2000 MXN -> 100 USD
|
|
91
|
+
render(MoneyDisplay, {
|
|
92
|
+
props: { amount: 2000, unit: "major", sourceCurrency: "MXN" },
|
|
93
|
+
context,
|
|
94
|
+
});
|
|
95
|
+
expect(screen.getByText(/\$100\.00/)).toBeInTheDocument();
|
|
96
|
+
});
|
|
97
|
+
it("shows source currency label when showSourceCurrency is true", () => {
|
|
98
|
+
render(MoneyDisplay, {
|
|
99
|
+
props: { amount: 2000, unit: "major", sourceCurrency: "MXN", showSourceCurrency: true },
|
|
100
|
+
context,
|
|
101
|
+
});
|
|
102
|
+
expect(screen.getByText("(MXN)")).toBeInTheDocument();
|
|
103
|
+
});
|
|
104
|
+
it("falls back to source currency when conversion is impossible", () => {
|
|
105
|
+
const contextNoConvert = new Map(context);
|
|
106
|
+
contextNoConvert.set(RUNE_LAB_CONTEXT.currency, {
|
|
107
|
+
...mockCurrencyStore,
|
|
108
|
+
canConvert: false
|
|
109
|
+
});
|
|
110
|
+
render(MoneyDisplay, {
|
|
111
|
+
props: { amount: 2000, unit: "major", sourceCurrency: "MXN" },
|
|
112
|
+
context: contextNoConvert,
|
|
113
|
+
});
|
|
114
|
+
// Should show $2,000.00 (MXN)
|
|
115
|
+
expect(screen.getByText(/\$2,000\.00/)).toBeInTheDocument();
|
|
116
|
+
});
|
|
74
117
|
});
|
|
@@ -3,25 +3,34 @@
|
|
|
3
3
|
No floats cross the boundary — values are always integer minor units.
|
|
4
4
|
-->
|
|
5
5
|
<script module lang="ts">
|
|
6
|
-
import type { ISO4217Code } from "
|
|
6
|
+
import type { ISO4217Code } from "@internal/core";
|
|
7
|
+
import { MoneyPrimitive } from "@internal/core";
|
|
7
8
|
|
|
8
9
|
export interface MoneyInputProps {
|
|
9
10
|
/** Current value in minor or major units (see unit prop) */
|
|
10
11
|
amount?: number | null | undefined;
|
|
11
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* MoneyPrimitive instance. When provided, initial amount and currency
|
|
14
|
+
* are derived from the primitive. Read-only input — output still fires oninput().
|
|
15
|
+
*/
|
|
16
|
+
money?: MoneyPrimitive;
|
|
17
|
+
/**
|
|
12
18
|
* Whether the amount is in 'major' (e.g., pesos) or 'minor' (e.g., centavos) units.
|
|
19
|
+
* @deprecated Use `money` prop with MoneyPrimitive instead.
|
|
13
20
|
* Defaults to 'minor' for backward compatibility.
|
|
14
21
|
*/
|
|
15
|
-
unit?:
|
|
22
|
+
unit?: "major" | "minor";
|
|
16
23
|
/** Override currency code (defaults to CurrencyStore.current) */
|
|
17
24
|
currency?: ISO4217Code | string;
|
|
18
|
-
/**
|
|
25
|
+
/** The currency the amount is stored in (e.g. MXN) */
|
|
26
|
+
storageCurrency?: ISO4217Code | string;
|
|
27
|
+
/** Minimum value in same units as amount (in storageCurrency) */
|
|
19
28
|
min?: number;
|
|
20
|
-
/** Maximum value in same units as amount */
|
|
29
|
+
/** Maximum value in same units as amount (in storageCurrency) */
|
|
21
30
|
max?: number;
|
|
22
31
|
/** Placeholder text */
|
|
23
32
|
placeholder?: string;
|
|
24
|
-
/** Fired when the value changes (unit matches the unit prop) */
|
|
33
|
+
/** Fired when the value changes (unit matches the unit prop, in storageCurrency) */
|
|
25
34
|
oninput?: (val: number) => void;
|
|
26
35
|
/** Input disabled state */
|
|
27
36
|
disabled?: boolean;
|
|
@@ -29,13 +38,16 @@
|
|
|
29
38
|
</script>
|
|
30
39
|
|
|
31
40
|
<script lang="ts">
|
|
32
|
-
import { getCurrencyStore } from "
|
|
33
|
-
import { toMinorUnit } from "
|
|
41
|
+
import { getCurrencyStore } from "@internal/state";
|
|
42
|
+
import { toMinorUnit } from "@internal/core";
|
|
43
|
+
import { DEV } from "esm-env";
|
|
34
44
|
|
|
35
45
|
let {
|
|
36
46
|
amount = $bindable(0),
|
|
37
|
-
|
|
47
|
+
money,
|
|
48
|
+
unit = "minor",
|
|
38
49
|
currency,
|
|
50
|
+
storageCurrency,
|
|
39
51
|
min,
|
|
40
52
|
max,
|
|
41
53
|
placeholder = "0.00",
|
|
@@ -45,19 +57,60 @@
|
|
|
45
57
|
|
|
46
58
|
const currencyStore = getCurrencyStore();
|
|
47
59
|
|
|
48
|
-
|
|
49
|
-
|
|
60
|
+
// Dev warning for deprecated usage
|
|
61
|
+
$effect(() => {
|
|
62
|
+
if (DEV && money && unit !== "minor") {
|
|
63
|
+
console.warn(
|
|
64
|
+
"[MoneyInput] The `unit` prop is deprecated when using `money: MoneyPrimitive`. " +
|
|
65
|
+
"The unit is derived from the MoneyPrimitive instance.",
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Seed initial values from MoneyPrimitive if provided
|
|
71
|
+
$effect(() => {
|
|
72
|
+
if (money && amount === 0) {
|
|
73
|
+
amount = money.minor;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const resolvedDisplayCurrency = $derived(
|
|
78
|
+
money?.currencyCode ?? currency ?? String(currencyStore.current),
|
|
50
79
|
);
|
|
51
80
|
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
81
|
+
const resolvedStorageCurrency = $derived(
|
|
82
|
+
storageCurrency ?? money?.currencyCode ?? resolvedDisplayCurrency,
|
|
83
|
+
);
|
|
55
84
|
|
|
56
|
-
|
|
57
|
-
|
|
85
|
+
const displayMeta = $derived(currencyStore.get(resolvedDisplayCurrency));
|
|
86
|
+
const symbol = $derived(displayMeta?.symbol ?? "$");
|
|
87
|
+
const decimals = $derived(displayMeta?.decimals ?? 2);
|
|
88
|
+
|
|
89
|
+
// The display value is reactive to amount (which is in storage currency)
|
|
58
90
|
const displayValue = $derived.by(() => {
|
|
59
|
-
|
|
60
|
-
|
|
91
|
+
let val = amount ?? 0;
|
|
92
|
+
|
|
93
|
+
// If storage !== display, convert for display
|
|
94
|
+
if (
|
|
95
|
+
resolvedStorageCurrency !== resolvedDisplayCurrency &&
|
|
96
|
+
currencyStore.canConvert
|
|
97
|
+
) {
|
|
98
|
+
const minorAmount =
|
|
99
|
+
unit === "major"
|
|
100
|
+
? toMinorUnit(val, resolvedStorageCurrency)
|
|
101
|
+
: val;
|
|
102
|
+
const convertedMinor = currencyStore.convertAmount(
|
|
103
|
+
Number(minorAmount),
|
|
104
|
+
resolvedStorageCurrency,
|
|
105
|
+
resolvedDisplayCurrency,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
if (decimals === 0) return String(Math.round(convertedMinor));
|
|
109
|
+
return (convertedMinor / Math.pow(10, decimals)).toFixed(decimals);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Standard display (no conversion or no rates)
|
|
113
|
+
if (unit === "major") {
|
|
61
114
|
return Number(val).toFixed(decimals);
|
|
62
115
|
}
|
|
63
116
|
if (decimals === 0) return String(val);
|
|
@@ -69,7 +122,11 @@
|
|
|
69
122
|
const target = e.target as HTMLInputElement;
|
|
70
123
|
const raw = target.value.replace(/[^0-9.,-]/g, "");
|
|
71
124
|
|
|
72
|
-
if (!raw)
|
|
125
|
+
if (!raw) {
|
|
126
|
+
amount = 0;
|
|
127
|
+
oninput?.(0);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
73
130
|
|
|
74
131
|
// String-based parsing: split on decimal point
|
|
75
132
|
const parts = raw.split(".");
|
|
@@ -79,33 +136,57 @@
|
|
|
79
136
|
let fractionalPart = (parts[1] || "").slice(0, decimals);
|
|
80
137
|
fractionalPart = fractionalPart.padEnd(decimals, "0");
|
|
81
138
|
|
|
82
|
-
// Combine as integer minor units
|
|
139
|
+
// Combine as integer minor units (in display currency)
|
|
83
140
|
const combined =
|
|
84
141
|
decimals === 0 ? integerPart : integerPart + fractionalPart;
|
|
85
142
|
|
|
86
|
-
let
|
|
87
|
-
if (isNaN(
|
|
88
|
-
|
|
89
|
-
//
|
|
90
|
-
let
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
143
|
+
let displayCents = parseInt(combined, 10);
|
|
144
|
+
if (isNaN(displayCents)) return;
|
|
145
|
+
|
|
146
|
+
// Convert back to storage currency
|
|
147
|
+
let storageCents = displayCents;
|
|
148
|
+
if (
|
|
149
|
+
resolvedStorageCurrency !== resolvedDisplayCurrency &&
|
|
150
|
+
currencyStore.canConvert
|
|
151
|
+
) {
|
|
152
|
+
storageCents = currencyStore.convertAmount(
|
|
153
|
+
displayCents,
|
|
154
|
+
resolvedDisplayCurrency,
|
|
155
|
+
resolvedStorageCurrency,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Apply constraints in storage currency minor units
|
|
160
|
+
let finalStorageCents = storageCents;
|
|
161
|
+
|
|
162
|
+
const storageDecimals =
|
|
163
|
+
currencyStore.get(resolvedStorageCurrency)?.decimals ?? 2;
|
|
164
|
+
const minCents =
|
|
165
|
+
min !== undefined
|
|
166
|
+
? unit === "major"
|
|
167
|
+
? toMinorUnit(min, resolvedStorageCurrency)
|
|
168
|
+
: min
|
|
169
|
+
: undefined;
|
|
170
|
+
const maxCents =
|
|
171
|
+
max !== undefined
|
|
172
|
+
? unit === "major"
|
|
173
|
+
? toMinorUnit(max, resolvedStorageCurrency)
|
|
174
|
+
: max
|
|
175
|
+
: undefined;
|
|
176
|
+
|
|
177
|
+
if (minCents !== undefined)
|
|
178
|
+
finalStorageCents = Math.max(finalStorageCents, minCents);
|
|
179
|
+
if (maxCents !== undefined)
|
|
180
|
+
finalStorageCents = Math.min(finalStorageCents, maxCents);
|
|
181
|
+
|
|
182
|
+
if (unit === "major") {
|
|
183
|
+
const majorValue =
|
|
184
|
+
finalStorageCents / Math.pow(10, storageDecimals);
|
|
101
185
|
amount = majorValue;
|
|
102
186
|
oninput?.(majorValue);
|
|
103
187
|
} else {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
amount = finalCents;
|
|
108
|
-
oninput?.(finalCents);
|
|
188
|
+
amount = finalStorageCents;
|
|
189
|
+
oninput?.(finalStorageCents);
|
|
109
190
|
}
|
|
110
191
|
}
|
|
111
192
|
</script>
|
|
@@ -123,9 +204,9 @@
|
|
|
123
204
|
{placeholder}
|
|
124
205
|
{disabled}
|
|
125
206
|
oninput={handleInput}
|
|
126
|
-
aria-label={`Amount in ${
|
|
207
|
+
aria-label={`Amount in ${resolvedDisplayCurrency}`}
|
|
127
208
|
/>
|
|
128
209
|
<span class="text-base-content/30 text-xs font-mono select-none"
|
|
129
|
-
>{
|
|
210
|
+
>{resolvedDisplayCurrency}</span
|
|
130
211
|
>
|
|
131
212
|
</label>
|
|
@@ -1,21 +1,30 @@
|
|
|
1
|
-
import type { ISO4217Code } from "
|
|
1
|
+
import type { ISO4217Code } from "@internal/core";
|
|
2
|
+
import { MoneyPrimitive } from "@internal/core";
|
|
2
3
|
export interface MoneyInputProps {
|
|
3
4
|
/** Current value in minor or major units (see unit prop) */
|
|
4
5
|
amount?: number | null | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* MoneyPrimitive instance. When provided, initial amount and currency
|
|
8
|
+
* are derived from the primitive. Read-only input — output still fires oninput().
|
|
9
|
+
*/
|
|
10
|
+
money?: MoneyPrimitive;
|
|
5
11
|
/**
|
|
6
12
|
* Whether the amount is in 'major' (e.g., pesos) or 'minor' (e.g., centavos) units.
|
|
13
|
+
* @deprecated Use `money` prop with MoneyPrimitive instead.
|
|
7
14
|
* Defaults to 'minor' for backward compatibility.
|
|
8
15
|
*/
|
|
9
|
-
unit?:
|
|
16
|
+
unit?: "major" | "minor";
|
|
10
17
|
/** Override currency code (defaults to CurrencyStore.current) */
|
|
11
18
|
currency?: ISO4217Code | string;
|
|
12
|
-
/**
|
|
19
|
+
/** The currency the amount is stored in (e.g. MXN) */
|
|
20
|
+
storageCurrency?: ISO4217Code | string;
|
|
21
|
+
/** Minimum value in same units as amount (in storageCurrency) */
|
|
13
22
|
min?: number;
|
|
14
|
-
/** Maximum value in same units as amount */
|
|
23
|
+
/** Maximum value in same units as amount (in storageCurrency) */
|
|
15
24
|
max?: number;
|
|
16
25
|
/** Placeholder text */
|
|
17
26
|
placeholder?: string;
|
|
18
|
-
/** Fired when the value changes (unit matches the unit prop) */
|
|
27
|
+
/** Fired when the value changes (unit matches the unit prop, in storageCurrency) */
|
|
19
28
|
oninput?: (val: number) => void;
|
|
20
29
|
/** Input disabled state */
|
|
21
30
|
disabled?: boolean;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<!-- src/client/sdk/ui/src/features/config/CommandPalette.svelte -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import { onMount, tick } from "svelte";
|
|
4
|
-
import { getCommandStore, type Command } from "
|
|
5
|
-
import { getShortcutStore } from "
|
|
6
|
-
import { Icon } from "
|
|
4
|
+
import { getCommandStore, type Command } from "@internal/state";
|
|
5
|
+
import { getShortcutStore } from "@internal/state";
|
|
6
|
+
import { Icon } from "../../../mod";
|
|
7
7
|
|
|
8
8
|
let { shortcutKey = "shift+k" } = $props<{ shortcutKey?: string }>();
|
|
9
9
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Describes a single configuration dimension
|
|
3
|
+
* (e.g., theme, language, currency) for the generic selector system.
|
|
4
|
+
*/
|
|
5
|
+
export interface ConfigDimension {
|
|
6
|
+
/** Unique key matching the RUNE_LAB_CONTEXT key */
|
|
7
|
+
readonly key: string;
|
|
8
|
+
/** Key used to look up the ConfigStore from context */
|
|
9
|
+
readonly storeKey: string;
|
|
10
|
+
/** Property name used as the item identifier */
|
|
11
|
+
readonly idKey: string;
|
|
12
|
+
/** Emoji used for display / logging */
|
|
13
|
+
readonly icon: string;
|
|
14
|
+
/** Human-readable label for settings panels */
|
|
15
|
+
readonly label: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* All built-in configuration dimensions.
|
|
19
|
+
* Used by `AppSettingSelector` and settings panels to enumerate available
|
|
20
|
+
* config pickers without hard-coding them.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```svelte
|
|
24
|
+
* {#each APP_CONFIGURATIONS as dim}
|
|
25
|
+
* <ResourceSelector store={stores[dim.storeKey]} idKey={dim.idKey} ... />
|
|
26
|
+
* {/each}
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare const APP_CONFIGURATIONS: readonly ConfigDimension[];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// sdk/ui/src/lib/features/config/APP_CONFIGURATIONS.ts
|
|
2
|
+
// Single source of truth for all configuration dimensions.
|
|
3
|
+
// Frozen at import time — must not be mutated at runtime.
|
|
4
|
+
/**
|
|
5
|
+
* All built-in configuration dimensions.
|
|
6
|
+
* Used by `AppSettingSelector` and settings panels to enumerate available
|
|
7
|
+
* config pickers without hard-coding them.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```svelte
|
|
11
|
+
* {#each APP_CONFIGURATIONS as dim}
|
|
12
|
+
* <ResourceSelector store={stores[dim.storeKey]} idKey={dim.idKey} ... />
|
|
13
|
+
* {/each}
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export const APP_CONFIGURATIONS = Object.freeze([
|
|
17
|
+
{
|
|
18
|
+
key: "theme",
|
|
19
|
+
storeKey: "theme",
|
|
20
|
+
idKey: "name",
|
|
21
|
+
icon: "🎨",
|
|
22
|
+
label: "Theme",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
key: "language",
|
|
26
|
+
storeKey: "language",
|
|
27
|
+
idKey: "code",
|
|
28
|
+
icon: "🌍",
|
|
29
|
+
label: "Language",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
key: "currency",
|
|
33
|
+
storeKey: "currency",
|
|
34
|
+
idKey: "code",
|
|
35
|
+
icon: "💰",
|
|
36
|
+
label: "Currency",
|
|
37
|
+
},
|
|
38
|
+
]);
|