@linxin666/dsh-usage 0.4.10 → 0.4.12

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.
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Family-shared chrome for plugin settings cards: a disclosure header naming
3
+ * the plugin and what its settings govern, the controls inside, and the line
4
+ * that reports a refused write. A pick is written the moment it is made and a
5
+ * typed value after a pause, so the card has no save button and nothing waits
6
+ * on the reader. Renders nothing while the namespace is unavailable — a
7
+ * deployment that does not compose the owning plugin should show no trace of
8
+ * it. Inlined into each consumer's client bundle; mirrors the official
9
+ * ui-plugin-config PluginCard in a self-contained slice.
10
+ *
11
+ * Every field inside the body is one settings row: the label and its hint on
12
+ * the left, the control on the right, a hairline between two rows; a narrow
13
+ * card stacks the control under the text (settings-card.module.css).
14
+ */
15
+ import { type ReactNode } from 'react';
16
+ import type { CardShell } from './settings-form.ts';
17
+ /** Copy keys the card chrome itself reads; every consumer locale carries this shared vocabulary. */
18
+ export declare const CARD_COPY_KEYS: readonly ["settings.collapse", "settings.expand", "settings.notExposed", "settings.readOnly", "settings.saveFailed"];
19
+ /** Copy key the card chrome itself reads. */
20
+ export type CardCopyKey = (typeof CARD_COPY_KEYS)[number];
21
+ /** Key domain for the plugin's own copy (inferred per consumer). */
22
+ export type SettingsCardKey<TKey extends string = string> = TKey | CardCopyKey;
23
+ /** Card chrome shared by every plugin settings card. */
24
+ export interface PluginSettingsCardProps<TKey extends string = string> {
25
+ /** Locale reader for this card's copy. */
26
+ t: (key: SettingsCardKey<TKey>, params?: Record<string, string | number>) => string;
27
+ /** Locale key of the plugin's name. */
28
+ titleKey: TKey;
29
+ /** Locale key of the line describing what this plugin's settings govern. */
30
+ descriptionKey: TKey;
31
+ /** Optional rich header description rendered instead of the plain t(descriptionKey) text (the plain text stays as the tooltip). */
32
+ descriptionNode?: ReactNode;
33
+ /**
34
+ * Locale key of the full sentence behind a shortened description: when
35
+ * present, the header's native `title` takes this text while the visible
36
+ * line keeps `descriptionKey`/`descriptionNode`, so a compact card can
37
+ * compress the sentence without keeping a second visible copy of it.
38
+ * Defaults to the description key.
39
+ */
40
+ descriptionTitleKey?: TKey;
41
+ /**
42
+ * Optional leading mark identifying the plugin. A card without one keeps the
43
+ * plain official header shape, so the chrome stays a readable sibling of the
44
+ * built-in PluginCard wherever a family plugin lands in the official bundle
45
+ * seat.
46
+ */
47
+ icon?: ReactNode;
48
+ /** The card's form state: availability, writability, and how the last write went. */
49
+ state: CardShell;
50
+ /** Write every pending draft now: a text field's Enter or blur. */
51
+ onSave: () => void;
52
+ /** Drop every pending draft: a text field's Escape. */
53
+ onDiscard: () => void;
54
+ /**
55
+ * Id applied to the card's title element, so a header control (an immediate
56
+ * toggle in `headerAction`) can name itself through `aria-labelledby`
57
+ * instead of repeating the title as a string.
58
+ */
59
+ titleId?: string;
60
+ /**
61
+ * Render the header only, with no body region. A card whose whole setting is
62
+ * the header control (a switch whose explanation is the description) has no
63
+ * controls to lay out below it, and an empty body would draw a divider line
64
+ * with nothing under it.
65
+ */
66
+ headerOnly?: boolean;
67
+ /**
68
+ * Render the controls expanded on arrival (still collapsible). Defaults to
69
+ * true: promoted first-level sections show their settings immediately.
70
+ */
71
+ defaultOpen?: boolean;
72
+ /**
73
+ * Render without the collapse affordance: a static header and an always
74
+ * visible body. Used by first-level settings sections whose nav entry
75
+ * already provides the selection.
76
+ */
77
+ alwaysOpen?: boolean;
78
+ /**
79
+ * Hide the line under the body that reports a refused write: a card whose
80
+ * body applies its own changes (an embedded external settings section)
81
+ * reports its own failures.
82
+ */
83
+ hideFooter?: boolean;
84
+ /**
85
+ * Optional control rendered at the header's trailing edge, before the
86
+ * chevron. A card whose setting is one immediate flip (it
87
+ * applies outside the card's form) puts its ToggleSwitch here, so the control
88
+ * is reachable without opening the card and matches a plugin card's shape.
89
+ */
90
+ headerAction?: ReactNode;
91
+ /**
92
+ * Render children even when the settings namespace is not exposed by the
93
+ * host. The 'not exposed' notice still explains the card above them; a card
94
+ * that wants the rows alone hides it through `hideNotExposedNotice`.
95
+ */
96
+ renderChildrenWhenNotExposed?: boolean;
97
+ /** Hide the 'not exposed' notice, whichever branch renders the card body. */
98
+ hideNotExposedNotice?: boolean;
99
+ /**
100
+ * Compact chrome for a topic card rendered inside another card's body: a
101
+ * smaller title, a tighter header and body inset, and a one-line description
102
+ * whose full text stays on the header's own title. Defaults to the
103
+ * first-level chrome, which is unchanged.
104
+ */
105
+ density?: 'nested';
106
+ /** The plugin's controls. */
107
+ children: ReactNode;
108
+ }
109
+ /**
110
+ * Render one plugin settings card.
111
+ * @param props - the plugin's copy keys, its form state, and its controls.
112
+ * @returns the card, or nothing while the namespace is still loading.
113
+ */
114
+ export declare function PluginSettingsCard<TKey extends string = string>(props: PluginSettingsCardProps<TKey>): import("react").JSX.Element | null;
115
+ /** Props every field control needs regardless of its value type. */
116
+ export interface FieldProps {
117
+ /** Stable id associating the label with its control. */
118
+ id: string;
119
+ /** Visible label. */
120
+ label: string;
121
+ /** One-line explanation rendered under the control. */
122
+ hint: string;
123
+ /**
124
+ * Full sentence behind the visible hint, exposed as the paragraph's native
125
+ * `title` so a card can shorten the line without losing the text. Omitted:
126
+ * the paragraph carries no title attribute at all.
127
+ */
128
+ hintTitle?: string;
129
+ /** Draft text this control renders. */
130
+ text: string;
131
+ /** True when the field holds a user-layer entry over the inherited value. */
132
+ overridden: boolean;
133
+ /** True when the draft is not a value this field accepts. */
134
+ invalid: boolean;
135
+ /** Copy for the overridden badge. */
136
+ overriddenLabel: string;
137
+ /** Copy for the reset control. */
138
+ resetLabel: string;
139
+ /** Copy shown in place of the hint while the draft is invalid. */
140
+ invalidLabel: string;
141
+ /** Disables every control (read-only document, or an unavailable namespace). */
142
+ disabled: boolean;
143
+ /** Take new draft text; the card writes it as the field's entry rule says. */
144
+ onEdit: (text: string) => void;
145
+ /** Clear the field at once so it re-inherits the composition layer. */
146
+ onReset: () => void;
147
+ }
148
+ /**
149
+ * A typed value field. Its draft is written after a pause in typing, at once on
150
+ * Enter or when focus leaves, and Escape puts the stored value back. `numeric`
151
+ * only hints the keypad: which drafts a field accepts is decided by its spec.
152
+ * A field given a `range` also offers the coarse slider bubble: dragging fills
153
+ * the draft live and releasing writes it.
154
+ */
155
+ export declare function ValueField(props: FieldProps & {
156
+ /** Hints a numeric keypad without narrowing what the control accepts. */
157
+ numeric?: boolean;
158
+ /** Placeholder shown while the draft is empty. */
159
+ placeholder?: string;
160
+ /** Bounds of a numeric field, for the coarse slider; the spec still judges every draft. */
161
+ range?: {
162
+ min: number;
163
+ max: number;
164
+ step: number;
165
+ };
166
+ }): import("react").JSX.Element;
167
+ interface SelectOption {
168
+ value: string;
169
+ label: string;
170
+ }
171
+ /**
172
+ * The shared dual-mode select control. While an appearance skin is active it
173
+ * renders the legacy native `<select>` untouched, so element-level skin
174
+ * selectors keep working; under the default appearance it renders a
175
+ * self-drawn `role="listbox"` popup whose open/close is transition-animated.
176
+ * Cards reach it through BooleanField/ChoiceField; editors outside a card
177
+ * (the side-card prefs) bind it directly through onEdit.
178
+ * 双模式下拉框:皮肤激活时用原生 select,默认外观用自绘动画弹层。
179
+ */
180
+ export declare function SelectField(props: {
181
+ id: string;
182
+ options: ReadonlyArray<SelectOption>;
183
+ value: string;
184
+ disabled: boolean;
185
+ invalid: boolean;
186
+ onEdit: (text: string) => void;
187
+ }): import("react").JSX.Element;
188
+ /**
189
+ * A visible segmented group for a small set of mutually exclusive values.
190
+ * Every option stays on screen, so inherit/on/off and short enum fields take
191
+ * one click without opening a popup. The pressed option carries the group's
192
+ * sliding highlight and `aria-pressed`; the group takes the field label as its
193
+ * accessible name.
194
+ */
195
+ export declare function SegmentField(props: {
196
+ id: string;
197
+ label: string;
198
+ options: ReadonlyArray<SelectOption>;
199
+ value: string;
200
+ disabled: boolean;
201
+ invalid: boolean;
202
+ onEdit: (text: string) => void;
203
+ }): import("react").JSX.Element;
204
+ /** How a small single-choice field presents its options. */
205
+ export type FieldPresentation = 'select' | 'segment';
206
+ /** A boolean field: 继承 / 开 / 关. */
207
+ export declare function BooleanField(props: FieldProps & {
208
+ /** Copy for the inherit option. */
209
+ inheritLabel: string;
210
+ /** Copy for the on option. */
211
+ onLabel: string;
212
+ /** Copy for the off option. */
213
+ offLabel: string;
214
+ /** Defaults to the visible segments: the three answers always fit on one row. */
215
+ presentation?: FieldPresentation;
216
+ }): import("react").JSX.Element;
217
+ /** An enumerated field, selecting or segmenting its small choice set. */
218
+ export declare function ChoiceField(props: FieldProps & {
219
+ /** Copy for the inherit option (draft text is the empty string). */
220
+ inheritLabel: string;
221
+ /** Choices rendered in order; `value` is the draft/stored text. */
222
+ choices: ReadonlyArray<{
223
+ value: string;
224
+ label: string;
225
+ }>;
226
+ /**
227
+ * Visible segmented options for a short fixed set; defaults to the compact
228
+ * select, which also suits a list whose length follows the deployment.
229
+ */
230
+ presentation?: FieldPresentation;
231
+ }): import("react").JSX.Element;
232
+ /**
233
+ * A boolean toggle drawn as a switch track, for a setting that is only on or
234
+ * off and applies on flip. It is a real button with
235
+ * `role="switch"` and `aria-checked`, so keyboard and assistive activation
236
+ * arrive as a click and the state is announced instead of inferred from a
237
+ * checkbox tick.
238
+ *
239
+ * The thumb bounces across only once the reader has pressed this switch: the
240
+ * press writes the mark that arms the bounce, so a page opening, a TAB
241
+ * switching back or a value arriving from elsewhere draws every thumb at rest.
242
+ */
243
+ export declare function ToggleSwitch(props: {
244
+ /** Id of the element naming the switch (the card's title). */
245
+ labelledBy?: string;
246
+ /** Accessible name when no element labels it. */
247
+ ariaLabel?: string;
248
+ /** Current state. */
249
+ checked: boolean;
250
+ /** Disables the switch while a write is in flight or the Host refuses writes. */
251
+ disabled?: boolean;
252
+ /** Called with the requested next state. */
253
+ onChange: (next: boolean) => void;
254
+ }): import("react").JSX.Element;
255
+ export {};
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Coarse adjustment for a bounded number: resting the pointer on the control
3
+ * (or moving focus into it) raises a small bubble holding a slider across the
4
+ * field's whole range, so a large change is one drag instead of many presses
5
+ * or a retyped value. The control itself stays the precise entry.
6
+ *
7
+ * The bubble belongs to the anchor's subtree, so moving from the control onto
8
+ * the bubble never counts as leaving. It opens after a short rest and closes a
9
+ * moment after the pointer and focus have both gone, so crossing the control
10
+ * on the way elsewhere does not flash it. While a drag is in progress the
11
+ * bubble stays put even if the pointer strays off it.
12
+ *
13
+ * Dragging reports each value through `onInput`, so the field shows it live;
14
+ * releasing the thumb, or a key press on it, reports `onCommit` once. The
15
+ * bubble opens on the side of the control that has room inside the nearest
16
+ * scrolling ancestor, so a row near the top of a settings page does not hang
17
+ * its bubble under the page edge.
18
+ */
19
+ import { type ReactNode } from 'react';
20
+ /** Props of {@link RangeBubble}. */
21
+ export interface RangeBubbleProps {
22
+ /** Lowest value the slider reaches. */
23
+ min: number;
24
+ /** Highest value the slider reaches. */
25
+ max: number;
26
+ /** Slider granularity. */
27
+ step: number;
28
+ /** Current value; a value outside the range parks the thumb at the nearer end. */
29
+ value: number;
30
+ /** Accessible name of the slider. */
31
+ label: string;
32
+ /** Disables the bubble along with the control. */
33
+ disabled?: boolean;
34
+ /** Each value while the thumb moves. */
35
+ onInput: (value: number) => void;
36
+ /** The value the thumb was released on, once per drag or key press. */
37
+ onCommit: (value: number) => void;
38
+ /** The precise control the bubble hangs off. */
39
+ children: ReactNode;
40
+ }
41
+ /**
42
+ * Render the control with its coarse-adjustment bubble.
43
+ * @param props - the range, the value and the two report callbacks.
44
+ * @returns the anchor holding the control and the bubble.
45
+ */
46
+ export declare function RangeBubble(props: RangeBubbleProps): ReactNode;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The family's segmented control: a quiet track whose options share one
3
+ * highlight that slides to the pressed option, instead of each option fading
4
+ * its own background out and the next one in. The settings fields' small
5
+ * choices and the Web Extensions TAB strip both render through it, so the two
6
+ * read as one control.
7
+ *
8
+ * The caller builds the option buttons and marks the pressed one with
9
+ * `data-active`; the group measures that button after every render and writes
10
+ * its box as four custom properties, and the stylesheet draws the highlight
11
+ * there (`.segmentGroup[data-pill]::before`). Attribute and first placement
12
+ * land in the same task, so the highlight appears in place and only later
13
+ * changes slide. A ResizeObserver re-measures when the group or an option
14
+ * changes size: a longer locale, a font arriving, a container growing, a
15
+ * strip wrapping onto a second line.
16
+ */
17
+ import { type HTMLAttributes, type ReactNode } from 'react';
18
+ /** Props of {@link SegmentGroup}: the track's own attributes and the option buttons. */
19
+ export interface SegmentGroupProps extends HTMLAttributes<HTMLDivElement> {
20
+ /** The option buttons; the pressed one carries `data-active`. */
21
+ children: ReactNode;
22
+ }
23
+ /**
24
+ * Render a segmented track around the caller's option buttons.
25
+ * @param props - the track's attributes (role, accessible name, key handler) and its options.
26
+ * @returns the track.
27
+ */
28
+ export declare function SegmentGroup(props: SegmentGroupProps): ReactNode;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The usage page's card chrome: the family settings card whose header holds
3
+ * the plugin switch, and the display rows the body closes with.
4
+ * @module @linxin666/dsh-usage/client/UsagePage
5
+ */
6
+ import type { ReactNode } from 'react';
7
+ import { type UsageSettingsCardFace, type UsageSettingsCardState } from './usage-settings-card.ts';
8
+ /**
9
+ * The page's card: the plugin switch in the header, the statistics and the
10
+ * display rows in the body. A switched-off plugin keeps only the header, whose
11
+ * line says how to bring it back; a refused write keeps the body, so the
12
+ * failure line under the rows stays readable.
13
+ */
14
+ export declare function UsagePage(props: {
15
+ card: UsageSettingsCardState;
16
+ face: UsageSettingsCardFace;
17
+ enabled: boolean;
18
+ children: ReactNode;
19
+ }): ReactNode;
@@ -1,25 +1,18 @@
1
1
  /**
2
- * The usage statistics settings section: three tabs (用量: today's usage,
3
- * balances, trend; 个人套餐: per-provider plan quota windows; Token 银行:
4
- * the whale-yuan voucher minted from every provider's ledger usage)
5
- * plus a compact settings row. Data comes from the host's loopback-fenced
6
- * /api/dsh-usage/overview document; polling runs only while the section is
7
- * mounted and the tab is visible.
2
+ * The usage statistics settings page: one family settings card whose header
3
+ * carries the plugin switch, whose body holds three tabs (用量: today's
4
+ * usage, balances, trend; 个人套餐: per-provider plan quota windows; Token
5
+ * 银行: the whale-yuan voucher minted from every provider's ledger usage)
6
+ * and, under them, the display rows. Data comes from the host's
7
+ * loopback-fenced /api/dsh-usage/overview document; polling runs only while
8
+ * the section is mounted and the tab is visible.
8
9
  * @module @linxin666/dsh-usage/client/UsageSectionCard
9
10
  */
10
11
  import { type ReactNode } from 'react';
11
- import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client';
12
12
  import type { UsageStoreInstance } from './usage-store.ts';
13
+ import type { UsageSettingsCardFace } from './usage-settings-card.ts';
13
14
  import type { UsageDayView } from '../core/types.ts';
14
- /** The settings fields this section edits (immediate-apply semantics). */
15
- export interface UsageSettings {
16
- enabled?: boolean;
17
- pollIntervalSec?: number;
18
- /** Currency the spend figures are displayed in; the ledger stays CNY. */
19
- spendCurrency?: 'CNY' | 'USD';
20
- /** CNY per USD, consulted only while the display currency is USD. */
21
- cnyPerUsd?: number;
22
- }
15
+ export type { UsageSettings } from './usage-settings-card.ts';
23
16
  /** The registration-side face the section's slot entry injects. */
24
17
  export interface UsageSectionFace {
25
18
  /** The section-local store (overview snapshot + lifecycle). */
@@ -34,8 +27,8 @@ export interface UsageSectionFace {
34
27
  * is one small on-demand request.
35
28
  */
36
29
  loadDay: (date: string) => Promise<UsageDayView>;
37
- /** The shared configuration form this section's settings row reads and writes. */
38
- settings: ConfigForm<UsageSettings>;
30
+ /** The settings card over the usage namespace: the plugin switch and the display rows. */
31
+ settingsCard: UsageSettingsCardFace;
39
32
  }
40
33
  export interface UsageSectionProps extends UsageSectionFace {
41
34
  /** Close the settings panel (the shell owns the open state). */
@@ -55,11 +55,9 @@ export declare const zh: {
55
55
  'usage.provider.error': string;
56
56
  'usage.errorListSeparator': string;
57
57
  'usage.config.title': string;
58
- 'usage.config.enabled': string;
59
58
  'usage.config.pollIntervalSec': string;
60
59
  'usage.config.spendCurrency': string;
61
60
  'usage.config.cnyPerUsd': string;
62
- 'usage.config.saveFailed': string;
63
61
  'usage.bank.title': string;
64
62
  'usage.bank.hint': string;
65
63
  'usage.bank.noUsage': string;
@@ -76,6 +74,19 @@ export declare const zh: {
76
74
  'usage.foot.open': string;
77
75
  'usage.foot.collapse': string;
78
76
  'usage.foot.expand': string;
77
+ 'usage.description': string;
78
+ 'usage.config.pollIntervalHint': string;
79
+ 'usage.config.spendCurrencyHint': string;
80
+ 'usage.config.cnyPerUsdHint': string;
81
+ 'settings.collapse': string;
82
+ 'settings.expand': string;
83
+ 'settings.notExposed': string;
84
+ 'settings.readOnly': string;
85
+ 'settings.saveFailed': string;
86
+ 'settings.inherit': string;
87
+ 'settings.overridden': string;
88
+ 'settings.reset': string;
89
+ 'settings.invalidNumber': string;
79
90
  };
80
91
  /** English mirror; every zh key present. */
81
92
  export declare const en: Record<UsageKey, string>;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Optional integration with the family Web Extensions parent.
3
+ *
4
+ * The parent service owns page mounting; this module contributes one page
5
+ * contract and keeps the original first-level section as the standalone
6
+ * fallback. Deep links still resolve the parent service first.
7
+ */
8
+ import { type SlotSeatReader } from './foot-card-mount.tsx';
9
+ import { type WebExtensionPageContext } from './web-extension-page.ts';
10
+ /** The original first-level settings seat and its usage entry id. */
11
+ export declare const USAGE_SETTINGS_SEAT = "settings.section";
12
+ export declare const USAGE_SECTION_ID = "dsh-usage";
13
+ /** The family section's keyed extension seat. */
14
+ export declare const WEB_UI_EXTENSION_SEAT = "web-ui.extension";
15
+ /** Optional navigation contract published by the family extensions plugin. */
16
+ export interface WebUiExtensionsService {
17
+ sectionId: 'web-ui-plugins';
18
+ label(): string;
19
+ open(id: string): boolean;
20
+ }
21
+ /** The context slice needed by the migrating section registration. */
22
+ export type UsageSectionContext = WebExtensionPageContext;
23
+ /** The context slice needed by the foot card's settings navigation. */
24
+ export interface UsageNavigationContext {
25
+ get?(name: string): unknown;
26
+ slots: SlotSeatReader;
27
+ }
28
+ /** The usage card contribution, independent of the seat that owns it. */
29
+ export interface UsageSectionRegistration {
30
+ order: number;
31
+ locale: string;
32
+ label(): string;
33
+ inject(): object;
34
+ component: unknown;
35
+ }
36
+ /** Read the optional service without assuming a particular cordis test double. */
37
+ export declare function getWebUiExtensions(ctx: {
38
+ get?(name: string): unknown;
39
+ }): WebUiExtensionsService | undefined;
40
+ /** Register the one usage page; disposal is owned by the calling plugin fiber. */
41
+ export declare function installUsageSection(ctx: UsageSectionContext, registration: UsageSectionRegistration): () => void;
42
+ /**
43
+ * Open usage through the family service first. If it declines, fall back to the
44
+ * native settings shell on the family section (or on the original usage row
45
+ * when the service is absent), preserving the matching localized nav label.
46
+ */
47
+ export declare function openIntegratedUsageSettings(ctx: UsageNavigationContext): void;