@linxin666/dsh-usage 0.4.11 → 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.
@@ -88,9 +88,13 @@ export interface PluginSettingsCardProps<TKey extends string = string> {
88
88
  * is reachable without opening the card and matches a plugin card's shape.
89
89
  */
90
90
  headerAction?: ReactNode;
91
- /** Render children even when the settings namespace is not exposed by the host. */
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
+ */
92
96
  renderChildrenWhenNotExposed?: boolean;
93
- /** Hide the 'not exposed' warning banner when the settings namespace is not exposed. */
97
+ /** Hide the 'not exposed' notice, whichever branch renders the card body. */
94
98
  hideNotExposedNotice?: boolean;
95
99
  /**
96
100
  * Compact chrome for a topic card rendered inside another card's body: a
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@linxin666/dsh-usage",
3
3
  "description": "Usage statistics plugin for the dsh web GUI: per-provider balance and coding-plan quota detection plus a live token usage ledger, with a compact price glance card above the sidebar's Settings row",
4
- "version": "0.4.11",
4
+ "version": "0.4.12",
5
5
  "icon": "icon.svg",
6
6
  "type": "module",
7
7
  "engines": {
@@ -110,9 +110,13 @@ export interface PluginSettingsCardProps<TKey extends string = string> {
110
110
  * is reachable without opening the card and matches a plugin card's shape.
111
111
  */
112
112
  headerAction?: ReactNode
113
- /** Render children even when the settings namespace is not exposed by the host. */
113
+ /**
114
+ * Render children even when the settings namespace is not exposed by the
115
+ * host. The 'not exposed' notice still explains the card above them; a card
116
+ * that wants the rows alone hides it through `hideNotExposedNotice`.
117
+ */
114
118
  renderChildrenWhenNotExposed?: boolean
115
- /** Hide the 'not exposed' warning banner when the settings namespace is not exposed. */
119
+ /** Hide the 'not exposed' notice, whichever branch renders the card body. */
116
120
  hideNotExposedNotice?: boolean
117
121
  /**
118
122
  * Compact chrome for a topic card rendered inside another card's body: a
@@ -228,13 +232,16 @@ export function PluginSettingsCard<TKey extends string = string>(props: PluginSe
228
232
  // The namespace exists but the Host does not serve it to this client (the
229
233
  // official settings allowlist omits third-party namespaces): show a card
230
234
  // that explains the gap instead of vanishing, so a missing card never
231
- // reads as a missing plugin.
235
+ // reads as a missing plugin. A card that keeps its rows anyway keeps the
236
+ // same explanation above them, unless it hides the notice outright.
237
+ const notExposedNotice = state.exposed || props.hideNotExposedNotice === true
238
+ ? null
239
+ : <p className={css.notExposed} role="status">{props.t('settings.notExposed')}</p>
232
240
  if (!state.exposed && props.renderChildrenWhenNotExposed !== true) {
233
- const showNotice = props.hideNotExposedNotice !== true
234
241
  return (
235
242
  <li className={cardClass}>
236
243
  {header}
237
- {bodyRegion(showNotice ? <p className={css.notExposed} role="status">{props.t('settings.notExposed')}</p> : null)}
244
+ {bodyRegion(notExposedNotice)}
238
245
  </li>
239
246
  )
240
247
  }
@@ -247,6 +254,7 @@ export function PluginSettingsCard<TKey extends string = string>(props: PluginSe
247
254
  {bodyVisible
248
255
  ? bodyRegion(
249
256
  <>
257
+ {notExposedNotice}
250
258
  {!state.writable ? <p className={css.readOnly} role="status">{props.t('settings.readOnly')}</p> : null}
251
259
  <CardApply.Provider value={{ commit: props.onSave, revert: props.onDiscard }}>
252
260
  {props.children}
@@ -315,9 +323,15 @@ export function ValueField(props: FieldProps & {
315
323
  range?: { min: number, max: number, step: number }
316
324
  }) {
317
325
  const apply = useContext(CardApply)
326
+ // Enter and Escape settle the draft themselves and then leave the field, so
327
+ // the blur they cause is that same settlement reaching the field's edge, not
328
+ // a second write. Without this an Escape reported a discard and then a save,
329
+ // which reads to a consumer as a write still waiting to happen.
330
+ const settledByKey = useRef(false)
318
331
  const onKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
319
332
  if (apply === null || (event.key !== 'Enter' && event.key !== 'Escape')) return
320
333
  event.preventDefault()
334
+ settledByKey.current = true
321
335
  if (event.key === 'Enter') apply.commit()
322
336
  else apply.revert()
323
337
  event.currentTarget.blur()
@@ -334,7 +348,14 @@ export function ValueField(props: FieldProps & {
334
348
  disabled={props.disabled}
335
349
  onChange={(event) => { props.onEdit(event.target.value) }}
336
350
  onKeyDown={onKeyDown}
337
- onBlur={() => { apply?.commit() }}
351
+ onFocus={() => { settledByKey.current = false }}
352
+ onBlur={() => {
353
+ if (settledByKey.current) {
354
+ settledByKey.current = false
355
+ return
356
+ }
357
+ apply?.commit()
358
+ }}
338
359
  />
339
360
  )
340
361
  const { range } = props