@dungsil/dsh-plugin-ui-model-picker 0.2.1 → 0.2.2

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.
Files changed (2) hide show
  1. package/dist/client.js +86 -82
  2. package/package.json +4 -5
package/dist/client.js CHANGED
@@ -4,29 +4,79 @@ let react = require("react");
4
4
  let react_dom = require("react-dom");
5
5
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
6
6
  let react_jsx_runtime = require("react/jsx-runtime");
7
+ // ../../tools/ui/src/client.ts
8
+ function useLocaleSnapshot(locale) {
9
+ const subscribe = (0, react.useCallback)((listener) => locale.subscribe(listener), [locale]);
10
+ const getSnapshot = (0, react.useCallback)(() => locale.getSnapshot(), [locale]);
11
+ return (0, react.useSyncExternalStore)(subscribe, getSnapshot);
12
+ }
13
+ /** Each effect owns its sheet, even when another plugin installs the same defaults. */
14
+ function installStyles(css) {
15
+ const style = document.createElement("style");
16
+ style.textContent = css;
17
+ document.head.append(style);
18
+ return () => style.remove();
19
+ }
20
+ // ../../tools/ui/src/icons/rotate-ccw.svg
21
+ var rotate_ccw_default = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M5 7a9 9 0 1 1-1 9M5 3v4h4\"/>\n</svg>\n";
22
+ // ../../tools/ui/src/icons.ts
23
+ function IconResetOutlineRegular() {
24
+ return (0, react.createElement)("span", {
25
+ style: { display: "inline-flex" },
26
+ "aria-hidden": "true",
27
+ dangerouslySetInnerHTML: { __html: rotate_ccw_default }
28
+ });
29
+ }
30
+ // ../../tools/ui/src/composer.tsx
31
+ const composerStyles = `
32
+ :where(.dsh-plugin-composer-trigger) { display: flex; align-items: center; gap: 4px; min-width: 0; height: 28px; padding: 0 4px 0 8px; border: 0; border-radius: var(--dsw-radius-sm); background: transparent; color: var(--dsw-alias-label-secondary); font: inherit; font-size: 13px; line-height: 20px; cursor: pointer; }
33
+ :where(.dsh-plugin-composer-trigger):disabled { color: var(--dsw-alias-label-dimmed); cursor: default; }
34
+ .dsh-plugin-composer-icon, .dsh-plugin-composer-chevron { display: inline-flex; flex: none; }
35
+ .dsh-plugin-composer-label, .dsh-plugin-composer-detail { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
36
+ .dsh-plugin-composer-chevron, .dsh-plugin-composer-detail { color: var(--dsw-alias-label-caption); }
37
+ .dsh-plugin-composer-trigger[aria-expanded=true] .dsh-plugin-composer-chevron { transform: rotate(180deg); }
38
+ `;
39
+ const ComposerTrigger = (0, react.forwardRef)(function ComposerTrigger({ className = "", icon, label, detail, ...props }, ref) {
40
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
41
+ ...props,
42
+ ref,
43
+ type: "button",
44
+ className: `dsh-plugin-composer-trigger ${className}`,
45
+ children: [
46
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
47
+ className: "dsh-plugin-composer-icon",
48
+ "aria-hidden": "true",
49
+ children: icon
50
+ }),
51
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
52
+ className: "dsh-plugin-composer-label",
53
+ children: label
54
+ }),
55
+ detail && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
56
+ className: "dsh-plugin-composer-detail",
57
+ children: detail
58
+ }),
59
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
60
+ className: "dsh-plugin-composer-chevron",
61
+ "aria-hidden": "true",
62
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {})
63
+ })
64
+ ]
65
+ });
66
+ });
7
67
  // src/styles.ts
8
- const styles = `
68
+ const styles = `${composerStyles}
9
69
  .dsh-codex-picker { display: flex; align-items: center; min-width: 0; }
10
- .dsh-codex-trigger { display: flex; align-items: center; gap: 4px; min-width: 0; max-width: min(360px, 45cqw); height: 28px; padding: 0 4px 0 8px; border: none; border-radius: var(--dsw-radius-sm); background: transparent; color: var(--dsw-alias-label-secondary); font-family: inherit; font-size: 13px; font-weight: 400; line-height: 20px; cursor: pointer; outline: none; }
70
+ .dsh-codex-trigger { max-width: min(360px, 45cqw); font-weight: 400; outline: none; }
11
71
  .dsh-codex-trigger:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
12
72
  .dsh-codex-trigger:focus-visible:not([data-dsh-automatic-focus]) { box-shadow: 0 0 0 2px var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); }
13
- .dsh-codex-trigger:disabled { color: var(--dsw-alias-label-dimmed); cursor: default; }
14
- .dsh-codex-trigger-icon { display: var(--dsh-composer-model-icon-display, none); flex: none; }
15
- .dsh-codex-chevron { color: var(--dsw-alias-label-caption); flex: none; transition: transform .12s; }
16
- .dsh-codex-trigger[aria-expanded=true] .dsh-codex-chevron { transform: rotate(180deg); }
17
- .dsh-codex-name, .dsh-codex-effort { display: var(--dsh-composer-model-text-display, block); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
18
- .dsh-codex-effort { color: var(--dsw-alias-label-caption); flex-shrink: 1000; }
73
+ .dsh-codex-trigger .dsh-plugin-composer-icon { display: var(--dsh-composer-model-icon-display, none); }
74
+ .dsh-codex-trigger .dsh-plugin-composer-chevron { transition: transform .12s; }
75
+ .dsh-codex-trigger .dsh-plugin-composer-label, .dsh-codex-trigger .dsh-plugin-composer-detail { display: var(--dsh-composer-model-text-display, block); }
76
+ .dsh-codex-trigger .dsh-plugin-composer-detail { flex-shrink: 1000; }
19
77
  .dsh-codex-popover { position: fixed; z-index: 1100; display: flex; flex-direction: column; inset: auto; margin: 0; box-sizing: border-box; width: 256px; max-width: calc(100vw - 32px); max-height: min(360px, calc(100dvh - 96px)); overflow: hidden; padding: 4px; border: 0; --dsw-elevation-stroke-color: var(--dsw-alias-border-l1); --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2); --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2); background: transparent; color: var(--dsw-alias-label-primary); box-shadow: var(--dsw-elevation-prominent); font: 13px/20px var(--dsw-font-family, system-ui); }
20
- /* rc.5 has no MenuSurface phase API. Match the newer shared menu fades without
21
- animating the container: container opacity would change its backdrop blur.
22
- Old hosts forward phase to the div; newer hosts consume it for their own CSS. */
23
- .dsh-codex-popover[phase=enter] > * { animation: dsh-codex-menu-in 40ms ease-out; }
24
- .dsh-codex-popover[phase=dismiss] > * { animation: dsh-codex-menu-out 80ms ease-in both; }
25
- @keyframes dsh-codex-menu-in { from { opacity: 0; } to { opacity: 1; } }
26
- @keyframes dsh-codex-menu-out { from { opacity: 1; } to { opacity: 0; } }
27
78
  @media (prefers-reduced-motion: reduce) {
28
- .dsh-codex-popover[data-picker-phase] > * { animation-duration: 0ms; }
29
- .dsh-codex-chevron { transition: none; }
79
+ .dsh-codex-trigger .dsh-plugin-composer-chevron { transition: none; }
30
80
  }
31
81
  .dsh-codex-popover > :not([role=radiogroup]) { flex-shrink: 0; }
32
82
  .dsh-codex-popover [role=radiogroup] { min-height: 0; overflow-y: auto; }
@@ -38,6 +88,7 @@ const styles = `
38
88
  .dsh-codex-model > span { display: block; color: var(--dsw-alias-label-tertiary, #777); font-size: 12px; line-height: 17px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
39
89
  .dsh-codex-model:hover, .dsh-codex-reset:hover, .dsh-codex-back:hover { background: var(--dsw-alias-interactive-bg-hover, #f2f2f2); }
40
90
  .dsh-codex-reset { display: grid; place-items: center; width: 24px; height: 24px; color: var(--dsw-alias-label-tertiary, #999); }
91
+ .dsh-codex-reset svg { width: 16px; height: 16px; }
41
92
  .dsh-codex-back { padding: 4px 8px; color: var(--dsw-alias-label-tertiary, #777); }
42
93
  .dsh-codex-popover h2, .dsh-codex-provider { margin: 0; padding: 6px 10px; font-size: 12px; font-weight: 400; color: var(--dsw-alias-label-tertiary, #777); }
43
94
  .dsh-codex-provider { font-size: 11px; padding-top: 10px; }
@@ -117,11 +168,12 @@ const copy = {
117
168
  };
118
169
  function apply(ctx) {
119
170
  function Picker({ locked, directory }) {
120
- const state = (0, react.useSyncExternalStore)(directory.store.subscribe, directory.store.getSnapshot);
121
- const locale = (0, react.useSyncExternalStore)((fn) => ctx.locale.subscribe(fn), () => ctx.locale.getSnapshot().active);
171
+ const subscribe = (0, react.useCallback)((listener) => directory.store.subscribe(listener), [directory]);
172
+ const getSnapshot = (0, react.useCallback)(() => directory.store.getSnapshot(), [directory]);
173
+ const state = (0, react.useSyncExternalStore)(subscribe, getSnapshot);
174
+ const { active: locale } = useLocaleSnapshot(ctx.locale);
122
175
  const text = copy[locale.startsWith("ko") ? "ko" : locale.startsWith("zh") ? "zh" : "en"];
123
176
  const [panel, setPanel] = (0, react.useState)(null);
124
- const [exiting, setExiting] = (0, react.useState)(false);
125
177
  const [draft, setDraft] = (0, react.useState)(0);
126
178
  const [failed, setFailed] = (0, react.useState)(false);
127
179
  const trigger = (0, react.useRef)(null);
@@ -150,27 +202,10 @@ function apply(ctx) {
150
202
  };
151
203
  }, [directory]);
152
204
  const close = () => {
153
- setExiting(true);
205
+ setPanel(null);
154
206
  if (trigger.current) (0, _deepseek_ai_dsh_client_ui_primitives.focusWithoutRing)(trigger.current);
155
207
  };
156
- (0, _deepseek_ai_dsh_client_ui_primitives.useDismissOnOutsidePointer)(anchor, panel !== null && !exiting, () => setExiting(true), popup);
157
- (0, react.useLayoutEffect)(() => {
158
- if (!exiting || !popup.current) return;
159
- const element = popup.current;
160
- element.inert = true;
161
- let cancelled = false;
162
- const animations = element.getAnimations({ subtree: true }).filter((animation) => {
163
- const name = animation.animationName;
164
- return name?.includes("dsh-context-menu-out") || name === "dsh-codex-menu-out";
165
- });
166
- Promise.all(animations.map((animation) => animation.finished.catch(() => {}))).then(() => {
167
- if (!cancelled) setPanel(null);
168
- });
169
- return () => {
170
- cancelled = true;
171
- element.inert = false;
172
- };
173
- }, [exiting]);
208
+ (0, _deepseek_ai_dsh_client_ui_primitives.useDismissOnOutsidePointer)(anchor, panel !== null, () => setPanel(null), popup);
174
209
  (0, react.useLayoutEffect)(() => {
175
210
  if (!panel || !popup.current || !anchor.current) return;
176
211
  const element = popup.current;
@@ -216,10 +251,10 @@ function apply(ctx) {
216
251
  };
217
252
  }, [panel]);
218
253
  (0, react.useEffect)(() => {
219
- if (locked) setExiting(true);
254
+ if (locked) setPanel(null);
220
255
  }, [locked]);
221
256
  (0, react.useEffect)(() => {
222
- setExiting(true);
257
+ setPanel(null);
223
258
  }, [
224
259
  current?.provider,
225
260
  current?.model,
@@ -242,11 +277,10 @@ function apply(ctx) {
242
277
  };
243
278
  const show = (button) => {
244
279
  trigger.current = button;
245
- if (panel && !exiting) {
280
+ if (panel) {
246
281
  close();
247
282
  return;
248
283
  }
249
- setExiting(false);
250
284
  setDraft(Math.max(0, effortIndex));
251
285
  setFailed(false);
252
286
  setPanel("effort");
@@ -265,39 +299,23 @@ function apply(ctx) {
265
299
  className: "dsh-codex-picker",
266
300
  ref: anchor,
267
301
  "aria-busy": busy,
268
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
269
- type: "button",
302
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComposerTrigger, {
270
303
  className: "dsh-codex-trigger",
304
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDataOutlineRegular, { size: 16 }),
305
+ label: model?.name ?? current?.model ?? text.model,
306
+ detail: effortLabel,
271
307
  disabled,
272
308
  "aria-label": `${text.choose}: ${model?.name ?? current?.model ?? text.model}${effortLabel ? `, ${effortLabel}` : ""}`,
273
309
  title: `${model?.name ?? current?.model ?? text.model}${effortLabel ? ` ${effortLabel}` : ""}`,
274
310
  "aria-haspopup": "dialog",
275
- "aria-expanded": panel !== null && !exiting,
276
- "aria-controls": panel && !exiting ? id : void 0,
277
- onClick: (event) => show(event.currentTarget),
278
- children: [
279
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDataOutlineRegular, {
280
- className: "dsh-codex-trigger-icon",
281
- size: 16
282
- }),
283
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
284
- className: "dsh-codex-name",
285
- children: model?.name ?? current?.model ?? text.model
286
- }),
287
- effortLabel && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
288
- className: "dsh-codex-effort",
289
- children: effortLabel
290
- }),
291
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: "dsh-codex-chevron" })
292
- ]
311
+ "aria-expanded": panel !== null,
312
+ "aria-controls": panel ? id : void 0,
313
+ onClick: (event) => show(event.currentTarget)
293
314
  })
294
315
  }), panel && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.MenuSurface, {
295
316
  id,
296
317
  ref: popup,
297
318
  role: "dialog",
298
- "aria-hidden": exiting || void 0,
299
- "data-picker-phase": exiting ? "dismiss" : "enter",
300
- phase: exiting ? "dismiss" : "enter",
301
319
  "aria-label": panel === "model" ? text.model : text.effort,
302
320
  className: "dsh-codex-popover",
303
321
  children: [
@@ -335,16 +353,7 @@ function apply(ctx) {
335
353
  model: current.model
336
354
  });
337
355
  },
338
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
339
- width: "16",
340
- height: "16",
341
- viewBox: "0 0 16 16",
342
- fill: "none",
343
- stroke: "currentColor",
344
- strokeWidth: "1.2",
345
- "aria-hidden": "true",
346
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M3 4a5.5 5.5 0 1 1-1 6M3 1v3h3" })
347
- })
356
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconResetOutlineRegular, {})
348
357
  })]
349
358
  }),
350
359
  state.status === "loading" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
@@ -462,12 +471,7 @@ function apply(ctx) {
462
471
  locked: props.locked,
463
472
  directory: ctx.modelDirectories.directoryFor(props.sessionId)
464
473
  }, props.sessionId) : null));
465
- ctx.effect(() => {
466
- const style = document.createElement("style");
467
- style.textContent = styles;
468
- document.head.append(style);
469
- return () => style.remove();
470
- }, "ui-model-picker: styles");
474
+ ctx.effect(() => installStyles(styles), "ui-model-picker: styles");
471
475
  }
472
476
  exports.apply = apply;
473
477
  exports.inject = inject;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dungsil/dsh-plugin-ui-model-picker",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Model and reasoning effort picker for the DeepSeek Harness composer.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -32,14 +32,14 @@
32
32
  "@deepseek-ai/dsh-client-store": "0.2.1-rc.5",
33
33
  "@deepseek-ai/dsh-client-ui-conversation": "0.2.1-rc.5",
34
34
  "@deepseek-ai/dsh-client-ui-model-selection": "0.2.1-rc.5",
35
- "@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.5",
36
35
  "@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.5",
37
36
  "@deepseek-ai/dsh-client-ui-session": "0.2.1-rc.5",
38
37
  "@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.5",
39
38
  "@dungsil/dsh-tsconfig": "0.0.0",
39
+ "@dungsil/dsh-tsdown": "0.0.0",
40
+ "@dungsil/dsh-ui": "0.0.0",
40
41
  "@types/react": "18.3.31",
41
42
  "@types/react-dom": "18.3.7",
42
- "happy-dom": "21.0.4",
43
43
  "react": "18.3.1",
44
44
  "react-dom": "18.3.1",
45
45
  "tsdown": "0.23.4"
@@ -88,7 +88,6 @@
88
88
  },
89
89
  "scripts": {
90
90
  "build": "tsdown",
91
- "typecheck": "tsc --noEmit",
92
- "test": "node --test test/*.test.mjs"
91
+ "typecheck": "tsc --noEmit"
93
92
  }
94
93
  }