@jayyuen66/dsh-plugin-shared 0.2.3 → 0.2.4

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 CHANGED
@@ -11,8 +11,8 @@
11
11
  <a href="https://www.npmjs.com/package/@jayyuen66/dsh-plugin-shared"><img src="https://img.shields.io/npm/d18m/@jayyuen66/dsh-plugin-shared?style=flat-square" alt="npm downloads (total)" /></a>
12
12
  <a href="https://github.com/JayYuen666/dsh-plugin-shared/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/JayYuen666/dsh-plugin-shared/ci.yml?branch=main&style=flat-square&label=ci" alt="ci" /></a>
13
13
  <img src="https://img.shields.io/badge/license-MIT-4c1?style=flat-square" alt="MIT" />
14
- <img src="https://img.shields.io/badge/node-%3E%3D22.12-4c1?style=flat-square&logo=nodedotjs&logoColor=white" alt="node" />
15
- <img src="https://img.shields.io/badge/dsh-%5E0.2.1--rc.3-658EFF?style=flat-square" alt="dsh" />
14
+ <img src="https://img.shields.io/badge/node-%3E%3D24.9-4c1?style=flat-square&logo=nodedotjs&logoColor=white" alt="node" />
15
+ <img src="https://img.shields.io/badge/dsh-%5E0.2.1--rc.5-658EFF?style=flat-square" alt="dsh" />
16
16
  <a href="https://github.com/deepseek-ai/deepseek-harness"><img src="https://img.shields.io/badge/powered_by_dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white" alt="powered by dsh" /></a>
17
17
  </p>
18
18
 
@@ -46,9 +46,9 @@
46
46
 
47
47
  ## <img src="https://api.iconify.design/lucide/shield.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> What it does
48
48
 
49
- - **It is a library, not a plugin.** `package.json` carries no `dsh` field and the package has no `cordis.patch.yml`, so a copy in `node_modules` is never registered as a plugin. There is no settings card, no client side and no route of its own.
49
+ - **It is a library plus one client row, not a standalone bundle.** The host half is imported by the plugins; `package.json` declares `dsh.client` and exports `./client`, which each consumer enables from its own `cordis.patch.yml` with an id-less entry. It carries no `dsh.bundle.patch`, so installing the package alone adds no profile layer and no settings card or route. The id-less entry keeps the row out of the plugin page's per-component switches: switching it off would leave every settings card without its component supplier.
50
50
  - **It holds the parts the plugins repeated verbatim:**
51
- - The tool-event record table (`./lib/tool-events`): one scan from `SessionEvent[]` to call and result tables, PTC sub-calls included, plus `editPathOf` for the edit targets the record can see.
51
+ - The tool-event record table (`./lib/tool-events`): one scan from `SessionEvent[]` to call and result tables, PTC sub-calls included, plus `editPathOf` for the edit targets the record can see and `applyPatchTargetsOf` / `editPathsOf` for every write target in a multi-file `apply_patch` input, parsed by the official `dsh-fs-patch`.
52
52
  - Client card base components (`./lib/card-apply`, `./lib/card-diff`): an apply idempotency guard across HMR, and a form-value diff that treats `null`, `undefined` and an absent key as the same value.
53
53
  - Runtime utilities (`./lib/project-key`, `./lib/locale`, `./lib/lesson-bus`, `./lib/text`, `./lib/record`, `./lib/errors`, `./lib/jsonl`, `./lib/job-outcome`): the small logic every plugin would otherwise rewrite.
54
54
  - Build-time and check baselines (`./lib/canonicalize-region-paths`, `./config/*`): artifact path normalization plus the oxlint, vitest and tsconfig starting points for every consumer.
@@ -78,8 +78,8 @@ End users do not install or enable it separately: any consumer package pulls it
78
78
 
79
79
  | Requirement | Value |
80
80
  | --- | --- |
81
- | Node.js | `>=22.12.0` — the publish-side floor the plugin family shares, so a package declaring the same `engines.node` never rejects this library |
82
- | dsh | `^0.2.1-rc.3 || ^0.2.2-alpha.2`, declared as an **optional** peer (importing this package does not need a host) |
81
+ | Node.js | `>=24.9.0` — the publish-side floor the plugin family shares, so a package declaring the same `engines.node` never rejects this library |
82
+ | dsh | `^0.2.1-rc.5 || ^0.2.2-alpha.3`, declared as an **optional** peer (importing this package does not need a host) |
83
83
  | Module format | ESM only. `require()` works only where Node supports `require(esm)` |
84
84
  | Tree shaking | `"sideEffects": false` |
85
85
 
@@ -87,12 +87,13 @@ End users do not install or enable it separately: any consumer package pulls it
87
87
 
88
88
  ### Dependency requirements
89
89
 
90
- `dependencies` is empty; every `@deepseek-ai/*` entry lives only in `peerDependencies` and `devDependencies`. `test/publish-manifest.test.ts` enforces this, and the reason is a boot-time failure rather than style:
90
+ `dependencies` contains exactly one exception, `@deepseek-ai/dsh-fs-patch`; every other `@deepseek-ai/*` entry lives only in `peerDependencies` and `devDependencies`. `test/publish-manifest.test.ts` enforces this, and the reason is a boot-time failure rather than style:
91
91
 
92
+ - **`@deepseek-ai/dsh-fs-patch` is the one allowed runtime dependency.** It is a stateless pure parser: it holds no service instance or module-level identity, so a duplicate copy cannot split a `Symbol`. Shipping it makes `apply_patch` syntax resolvable on every host in the supported window. Every other host package stays in peer/dev.
92
93
  - **A host package under `dependencies` disables the consumer's host.** Profiles install with `nodeLinker: hoisted` and `autoInstallPeers: false`, so a listed host package is materialized as a real directory next to the copy the host process already loaded. `@deepseek-ai/dsh-tools` keys its tool-runtime scheduler with a plain `Symbol` (not `Symbol.for`), so symbol identity is per module instance: the host reads the scheduler from the duplicate and gets `undefined`, and every native tool call throws. Build, typecheck and tests pass up to that point; the invariant has to be stated in the manifest.
93
94
  - **`@deepseek-ai/dsh-output-retention` is the one required peer.** It is a value import (`truncateEnd`), provided by the host at runtime, and marked `"optional": false` in `peerDependenciesMeta` rather than left implicit — a peer left out of that table is a hard requirement for npm 7+.
94
95
  - **The remaining peers are optional.** Except for the bare `@deepseek-ai/dsh` peer, which only declares the host line, they carry type declarations only. `devDependencies` holds the exact development line, including `@deepseek-ai/dsh-llm` and `@deepseek-ai/dsh-util-values`, whose types the local PTC declarations name, and `@deepseek-ai/dsh-tool-executor-ptc`, whose types only the parity check reads. Profiles never install peers, so no duplicate appears.
95
- - **Version requirements separate the install gate from the build baseline.** Every `@deepseek-ai/dsh-*` peer carries the same window as `engines.dsh`, and that window names two host lines: a caret's prerelease comparison only covers its own patch tuple, so a single `^0.2.1-rc.3` does not admit `0.2.2-alpha.*` — measured with this workspace's semver, `satisfies('0.2.2-alpha.3','^0.2.1-rc.3')` is false and `{includePrerelease:true}` turns it true, and that flag is exactly what the host's own comparison passes. The cost is measured and belongs here: npm resolves each peer on its own, while the host family pins its own line inside every member (a member of the `0.2.1-rc` line asks cordis `~4.0.5`, the `0.2.2-alpha.2` host line asks `~4.0.6-alpha.1`, and `0.2.2-alpha.3` asks `~4.0.6-alpha.2`), so a consumer that pins the older host line can still stop at `ERESOLVE`, while the same tree resolves on the newest line. The union also admits later prereleases in the same `0.2.2` tuple, such as `0.2.2-rc.1`; that is range semantics, not a compatibility certification. The verified baseline for this release remains exactly `0.2.2-alpha.3`; moving to `rc.1` requires moving the dev pins and re-running the host parity check. pnpm — the installer this workspace supports, and the one behind the host's `dsh plugin add` — resolves both shapes and only prints a peer warning. The loading gate is unaffected either way, because the host compares `engines.dsh` with `includePrerelease: true`. `devDependencies` pin the exact line this repository compiles and tests against (`0.2.2-alpha.3`) and must stay uniform: one `dsh-session` version per tree keeps the nominal types recognizable. `@deepseek-ai/cordis` stays exact in `devDependencies` (`4.0.6-alpha.2`) and its peer range `^4.0.5||^4.0.6-alpha.1` covers both host lines, because one package cannot mix lines the way the host family does: alpha.2 requires `~4.0.6-alpha.1`, alpha.3 requires `~4.0.6-alpha.2`, and the range also admits `4.1.0`. The cordis union carries no spaces around `||`; the install-matrix probe reads one specifier per line now, while the dsh-family window keeps spaces. `test/publish-manifest.test.ts` checks each of these separately.
96
+ - **Version requirements separate the install gate from the build baseline.** Every `@deepseek-ai/dsh-*` peer carries the same window as `engines.dsh`, and that window names two host lines: a caret's prerelease comparison only covers its own patch tuple, so a single `^0.2.1-rc.5` does not admit `0.2.2-alpha.*` — measured with this workspace's semver, `satisfies('0.2.2-alpha.3','^0.2.1-rc.5')` is false and `{includePrerelease:true}` turns it true, and that flag is exactly what the host's own comparison passes. The cost is measured and belongs here: npm resolves each peer on its own, while the host family pins its own line inside every member (any member of the `0.2.1-rc` line asks cordis `~4.0.5`, the `0.2.2-alpha.2` host asks `~4.0.6-alpha.1`, `0.2.2-alpha.3` asks `~4.0.6-alpha.2`, and `0.2.2-rc.1` asks `~4.1.0`), so a consumer that pins the older host line can still stop at `ERESOLVE`, while the same tree resolves on the newest line. The union also admits later prereleases in the same `0.2.2` tuple, such as `0.2.2-rc.1`; that is range semantics, not a compatibility certification. The verified baseline for this release is `0.2.2-rc.1`; the dev pins and host parity check have moved to that line. pnpm — the installer this workspace supports, and the one behind the host's `dsh plugin add` — resolves both shapes and only prints a peer warning. The loading gate is unaffected either way, because the host compares `engines.dsh` with `includePrerelease: true`. `devDependencies` pin the exact line this repository compiles and tests against (`0.2.2-rc.1`) and must stay uniform: one `dsh-session` version per tree keeps the nominal types recognizable. `@deepseek-ai/cordis` stays exact in `devDependencies` (`4.1.0`) and its peer range `^4.0.5||^4.0.6-alpha.1` covers both host lines, because one package cannot mix lines the way the host family does: alpha.2 requires `~4.0.6-alpha.1`, alpha.3 requires `~4.0.6-alpha.2`, and the range also admits `4.1.0`. The cordis union carries no spaces around `||`; the install-matrix probe reads one specifier per line now, while the dsh-family window keeps spaces. `test/publish-manifest.test.ts` checks each of these separately.
96
97
  - Known upstream limitation: `@deepseek-ai/dsh-llm`'s declarations import `@deepseek-ai/dsh-attachment`, which that package does not declare. It is reached through `dsh-session`, so a consumer running with `skipLibCheck: false` sees `TS2307` from that package, not from this one. The published `./lib/tool-events` declaration also names `@deepseek-ai/dsh-util-values`, which is reached through `dsh-session` / `dsh-tools`; a consumer running with `skipLibCheck: false` must have that package resolvable as well.
97
98
 
98
99
  ### Shared version floor
@@ -117,8 +118,8 @@ A consumer profile can lock this package with a pnpm `overrides` entry. Lock it
117
118
 
118
119
  ## <img src="https://api.iconify.design/lucide/circle-slash.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> What it does not provide
119
120
 
120
- - **No plugin registration.** No `dsh` field, no `cordis.patch.yml`, no layer in `--dump-config`; `dsh plugin add` is the wrong action.
121
- - **No settings card or client side.** The package has no browser entry and no client-side state.
121
+ - **No bundle declaration.** It declares `dsh.client` for the client row but no `dsh.bundle.patch`; a copy in `node_modules` is not a profile layer by itself. Consumers enable the row from their own patch.
122
+ - **No settings card of its own.** The client row exports card components; the package registers no card, owns no client state and serves no route.
122
123
  - **No HTTP route and no request-trust checks.** The host's `/api` gateway owns authentication, the Host / Origin checks and body limits for the plugins that do register a route.
123
124
  - **No CLI subcommand and no model tool.**
124
125
  - **No domain judgement.** It does not know which tool counts as an edit, which path is secret, how large a budget is, or when a check should fail. Those belong to the calling plugin.
@@ -129,11 +130,13 @@ The subpath column is the in-package specifier; value imports prefix it with the
129
130
 
130
131
  | Subpath | Provides |
131
132
  | --- | --- |
132
- | `.` | Barrel over the runtime modules below. `canonicalize-region-paths` deliberately stays out, so a runtime consumer does not add an import for it; the client-only `card-ui` stays out for the same reason plus one more: the root barrel must not bring `react` into every consumer. |
133
- | `./lib/tool-events` | `scanToolEvents` returns the `calls` / `results` tables, PTC sub-calls included; `toolEventRowsOf` handles a single event and already carries normalized `arguments` and `badArguments`. `editPathOf` returns a `kind`-discriminated `EditTarget` with only `write` / `read-view`; the path is `undefined` when it cannot be read. Record fields are `readonly`. Re-exports the official `SessionEvent`; `PtcDispatchStartEventData` / `PtcDispatchEventData` are declared in this package and augment `SessionEventMap` locally, because the host moved that augmentation between packages inside the supported window. |
133
+ | `.` | Barrel over the runtime modules below, served from `dist/plugin.js`: the same names plus `name` and a no-op `apply`, so the host loader can also take this package as a row. `canonicalize-region-paths` deliberately stays out, so a runtime consumer does not add an import for it; the client-only `card-ui` stays out for the same reason plus one more: the root barrel must not bring `react` into every consumer. |
134
+ | `./lib/tool-events` | `scanToolEvents` returns the `calls` / `results` tables, PTC sub-calls included; `toolEventRowsOf` handles a single event and already carries normalized `arguments` and `badArguments`. `editPathOf` returns a `kind`-discriminated `EditTarget` with only `write` / `read-view`; the path is `undefined` when it cannot be read. `applyPatchTargetsOf` parses the ordered operations of an `apply_patch` input with the official `dsh-fs-patch`; `editPathsOf` expands every write target, including both sides of a move. Record fields are `readonly`. Re-exports the official `SessionEvent`; `PtcDispatchStartEventData` / `PtcDispatchEventData` are declared in this package and augment `SessionEventMap` locally, because the host moved that augmentation between packages inside the supported window. |
134
135
  | `./lib/card-apply` | `claimApply` + `CardApplyCtx`: an apply idempotency guard built on an **own** flag on `globalThis` (a symbol, or `true` written by an outside holder) with `ctx.effect` cleanup. The flag is process-wide, so a second context on the same page is rejected by design. |
135
136
  | `./lib/card-diff` | `diffTouched`: which touched fields really differ from the snapshot, compared by form value. `undefined`, `null` and an absent key are one value; arrays and objects compare element by element; cycles terminate. |
136
137
  | `./lib/card-ui` | Client-only settings-card kit: `CardForm`, `Section`, `FieldGrid`, `StatusBar`, `Field`, `FieldLabel`, `InfoTip`, `ToggleField`, `NumberField`, `TextField`, `ListField`, `SelectField`, `SaveBar`, `splitHint` and the `CARD_UI_CSS` string. Built with `createElement` only; `react` is an optional peer and the subpath never enters the root barrel. React 18 runtime APIs only (`useId`, `useState`, `useEffect`, refs). |
138
+ | `./client` | Browser client row: the `__ModuleLoader__` bundle the host module graph serves once. Consumers externalize this specifier and enable the row from their own patch with an id-less entry. |
139
+ | `./client-node` | Node/ESM face of the same client surface, used by tests and type-aware Node consumers. |
137
140
  | `./lib/project-key` | `deriveProjectKey` + `ProjectKeyOptions`: cwd → `last-segment-<first 8 hex of sha256(norm)>`, with `default` for a non-string, an empty string or a normalized root. `realpathSync` resolves symlinks; a failure falls back to the resolved path. Windows `\` and `/` produce the same key. |
138
141
  | `./lib/locale` | `Locale` (an alias of the official `BuiltInLocaleId`), `DEFAULT_LOCALE`, `resolveLocale`, `resolveLocalePreference`, `messagesFor`, `MessagesCatalog`, plus `LOCALE_SETTINGS_NAMESPACE` / `LOCALE_PREFERENCE_FIELD`. Host-side dictionaries only; the client uses the official locale package. |
139
142
  | `./lib/lesson-bus` | `settleLessonCall`: routes synchronous throws and asynchronous rejections into one `onFailure` exit. Function-valued thenables count as awaitable; an `onFailure` that itself throws or rejects is handled in the same place, so it never splits into "thrown at the caller" or "unhandled rejection". |
@@ -146,6 +149,8 @@ The subpath column is the in-package specifier; value imports prefix it with the
146
149
 
147
150
  `card-ui` is the one runtime module that is deliberately subpath-only: adding it to the barrel would make every host-side importer resolve `react`.
148
151
 
152
+ The client row is enabled by the consuming plugin, not by installing this package alone. The consumer writes `- name: "@jayyuen66/dsh-plugin-shared"` without an `id`; the host still loads it, while the plugin page does not expose it as a separately disableable component. An `id` would add that switch, and switching it off would break every settings card that requires the row's components.
153
+
149
154
  ## <img src="https://api.iconify.design/lucide/sliders-horizontal.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> The config exports
150
155
 
151
156
  These are lint/test baselines for plugin authors, not runtime code. They pull in tooling your package must provide itself:
@@ -153,7 +158,7 @@ These are lint/test baselines for plugin authors, not runtime code. They pull in
153
158
  | Subpath | Provides | You must install |
154
159
  | --- | --- | --- |
155
160
  | `./config/oxlint` | `definePluginConfig`, `makeTestFileOverrides`, `OFF_JUSTIFICATIONS` and the shared rule baseline | `oxlint`, `eslint-plugin-sonarjs`, `eslint-plugin-regexp`, `eslint-plugin-zod` (requires zod 4), `@e18e/eslint-plugin` |
156
- | `./config/vitest.base` | `definePackageConfig`: four 100% coverage thresholds, a 20-second test timeout, the shared coverage exclusions and the test include globs | `vitest` and `@vitest/coverage-v8` (the baseline sets `coverage.provider: "v8"`) |
161
+ | `./config/vitest.base` | `definePackageConfig`: four 100% coverage thresholds, a 20-second test timeout, the shared coverage exclusions, the test include globs, and the `@jayyuen66/dsh-plugin-shared/client` → `@jayyuen66/dsh-plugin-shared/client-node` alias (the UMD row reads `window` at load time, so a Node test cannot execute it) | `vitest` and `@vitest/coverage-v8` (the baseline sets `coverage.provider: "v8"`) |
157
162
  | `./config/tsconfig.base.json`, `./config/tsconfig.client.base.json` | Plain JSON for `extends` | Nothing |
158
163
 
159
164
  Those toolchain packages are **not** declared as `peerDependencies`: npm 7+ tries to satisfy optional peers too, and `oxlint`'s own `peerOptional vite-plus` locks `vitest` to a version disjoint from `>=5.0.2`, which made a plain `npm install` of this package fail with `ERESOLVE`. The requirement is stated here instead of in the manifest.
@@ -203,7 +208,7 @@ The package is imported into a host process; the table describes run time.
203
208
 
204
209
  | Question | Answer |
205
210
  | --- | --- |
206
- | Should I `dsh plugin add` it? | No. It has neither a config layer nor a client side, so there is nothing to enable. Declare it as a dependency of the plugin package. |
211
+ | Should I `dsh plugin add` it? | No. It declares `dsh.client` for the row consumers enable, but carries no `dsh.bundle.patch`, so adding it as a plugin installs no profile layer and enables nothing by itself. Declare it as a dependency of the plugin package, and insert the id-less row in your own `cordis.patch.yml`. |
207
212
  | A consumer boots with `does not provide an export named ...` and `N entries did not activate`. | The profile is serving an older copy of this package than the consumer imports. Raise the profile's `overrides` entry to a version that contains the subpath the consumer imports; check this package's `exports` and CHANGELOG. No per-repository CI run can see this state. |
208
213
  | I changed `lib/*.ts` and the consumer still runs the old behavior. | Consumers load `dist/`. Run `pnpm run build`; `test/build-host.test.ts` fails if the runtime artifact is stale. A workspace link does not bypass the artifact. |
209
214
  | Why is `@deepseek-ai/dsh-output-retention` a required peer while the rest are optional? | It is the only host package imported for value (`truncateEnd`). The rest are type-only, except the bare `@deepseek-ai/dsh` peer, which only declares the host line. |
@@ -216,7 +221,7 @@ The package is imported into a host process; the table describes run time.
216
221
  pnpm install
217
222
  pnpm run check # typecheck → lint → build → test → format check
218
223
  pnpm test # vitest with coverage
219
- pnpm run build # runtime artifacts, config artifacts, declarations
224
+ pnpm run build # runtime artifacts, config artifacts, declarations, client row
220
225
  ```
221
226
 
222
227
  - The suite splits by the class of defect a stub cannot see:
package/README.zh-CN.md CHANGED
@@ -11,8 +11,8 @@
11
11
  <a href="https://www.npmjs.com/package/@jayyuen66/dsh-plugin-shared"><img src="https://img.shields.io/npm/d18m/@jayyuen66/dsh-plugin-shared?style=flat-square" alt="npm downloads (total)" /></a>
12
12
  <a href="https://github.com/JayYuen666/dsh-plugin-shared/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/JayYuen666/dsh-plugin-shared/ci.yml?branch=main&style=flat-square&label=ci" alt="ci" /></a>
13
13
  <img src="https://img.shields.io/badge/license-MIT-4c1?style=flat-square" alt="MIT" />
14
- <img src="https://img.shields.io/badge/node-%3E%3D22.12-4c1?style=flat-square&logo=nodedotjs&logoColor=white" alt="node" />
15
- <img src="https://img.shields.io/badge/dsh-%5E0.2.1--rc.3-658EFF?style=flat-square" alt="dsh" />
14
+ <img src="https://img.shields.io/badge/node-%3E%3D24.9-4c1?style=flat-square&logo=nodedotjs&logoColor=white" alt="node" />
15
+ <img src="https://img.shields.io/badge/dsh-%5E0.2.1--rc.5-658EFF?style=flat-square" alt="dsh" />
16
16
  <a href="https://github.com/deepseek-ai/deepseek-harness"><img src="https://img.shields.io/badge/powered_by_dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white" alt="powered by dsh" /></a>
17
17
  </p>
18
18
 
@@ -46,9 +46,9 @@
46
46
 
47
47
  ## <img src="https://api.iconify.design/lucide/shield.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> 它做什么
48
48
 
49
- - **它是库,不是插件。** `package.json` 里没有 `dsh` 字段,包目录下也没有 `cordis.patch.yml`,所以位于 `node_modules` 的副本不会被宿主登记成插件。它没有设置卡,没有客户端部分,也没有自己的路由。
49
+ - **它是库加一条客户端行,不是独立组合包。** 宿主半由各插件 import;`package.json` 声明 `dsh.client` 并导出 `./client`,由各消费者在自己的 `cordis.patch.yml` 里以**无 id** 条目启用。它没有 `dsh.bundle.patch`,所以单独安装本包不会新增 profile 层、设置卡或路由。无 id 条目让插件页不把这条行列为可单独停用的组件:关掉它会让所有设置卡失去组件供应。
50
50
  - **它收录的是各插件逐字重复的部分:**
51
- - 工具事件记录表(`./lib/tool-events`):一次扫描把 `SessionEvent[]` 变成调用表与结果表,PTC 子调用一并合并;`editPathOf` 提取其中可见的编辑目标。
51
+ - 工具事件记录表(`./lib/tool-events`):一次扫描把 `SessionEvent[]` 变成调用表与结果表,PTC 子调用一并合并;`editPathOf` 提取其中可见的编辑目标,`applyPatchTargetsOf` / `editPathsOf` 用官方 `dsh-fs-patch` 解析多文件补丁并展开全部写目标。
52
52
  - 客户端卡片基础组件(`./lib/card-apply`、`./lib/card-diff`):跨 HMR 保证 apply 幂等的保护逻辑,以及把 `null`、`undefined` 与键缺失当成同一种值的表单差异比较。
53
53
  - 运行期工具(`./lib/project-key`、`./lib/locale`、`./lib/lesson-bus`、`./lib/text`、`./lib/record`、`./lib/errors`、`./lib/jsonl`、`./lib/job-outcome`):各个插件原本都要重写一遍的小逻辑。
54
54
  - 构建期与检查基线(`./lib/canonicalize-region-paths`、`./config/*`):产物路径规范化,加上给每个消费方的 oxlint、vitest 与 tsconfig 起点。
@@ -78,8 +78,8 @@ import { isRecord, fieldOf } from "@jayyuen66/dsh-plugin-shared/lib/record";
78
78
 
79
79
  | 要求 | 值 |
80
80
  | --- | --- |
81
- | Node.js | `>=22.12.0`——插件家族共用的发布侧下限,声明同一个 `engines.node` 的包不会因为这个库而被拒 |
82
- | dsh | `^0.2.1-rc.3 || ^0.2.2-alpha.2`,声明为 **optional** peer(import 本包不需要宿主存在) |
81
+ | Node.js | `>=24.9.0`——插件家族共用的发布侧下限,声明同一个 `engines.node` 的包不会因为这个库而被拒 |
82
+ | dsh | `^0.2.1-rc.5 || ^0.2.2-alpha.3`,声明为 **optional** peer(import 本包不需要宿主存在) |
83
83
  | 模块形态 | 仅 ESM。`require()` 只在支持 `require(esm)` 的 Node 上可用 |
84
84
  | Tree shaking | `"sideEffects": false` |
85
85
 
@@ -87,12 +87,13 @@ import { isRecord, fieldOf } from "@jayyuen66/dsh-plugin-shared/lib/record";
87
87
 
88
88
  ### 依赖要求
89
89
 
90
- `dependencies` 为空;所有 `@deepseek-ai/*` 只出现在 `peerDependencies` 与 `devDependencies` 里。`test/publish-manifest.test.ts` 固定了这条要求,理由是一次启动期故障,不是风格偏好:
90
+ `dependencies` 只有 `@deepseek-ai/dsh-fs-patch` 一枚纯解析器例外;其余 `@deepseek-ai/*` 只出现在 `peerDependencies` 与 `devDependencies` 里。`test/publish-manifest.test.ts` 固定了这条要求,理由是一次启动期故障,不是风格偏好:
91
91
 
92
+ - **`@deepseek-ai/dsh-fs-patch` 是唯一允许的普通依赖。** 它是无状态纯解析器,不承载服务实例或模块级身份,多一份不会分裂 `Symbol`;随包安装是为了让宿主窗口内没有该包的版本也能解析 `apply_patch` 语法。其余宿主包仍然必须留在 peer/dev。
92
93
  - **宿主包写进 `dependencies` 会让消费方的宿主无法工作。** profile 用 `nodeLinker: hoisted` 与 `autoInstallPeers: false` 安装,于是列在 `dependencies` 里的宿主包会被安装成真实目录,与宿主进程已加载的那份并存。`@deepseek-ai/dsh-tools` 的工具运行期调度键是普通 `Symbol`(不是 `Symbol.for`),符号身份按模块实例计算:宿主从副本上读那个键得到 `undefined`,于是每一次原生工具调用都抛错。构建、类型检查与测试在此之前全部通过;这条不变式只能写在 manifest 上。
93
94
  - **`@deepseek-ai/dsh-output-retention` 是唯一必需 peer。** 它是值导入(`truncateEnd`),运行期由宿主提供;在 `peerDependenciesMeta` 里明写成 `"optional": false`,不靠"没写就等于必需",因为没写进那张表对 npm 7+ 就是硬要求。
94
95
  - **其余 peer 都是 optional。** 除裸 `@deepseek-ai/dsh`(只声明宿主版本线)外,它们只承载类型声明。`devDependencies` 保存精确的开发线,其中包括本包 PTC 声明所引用的 `@deepseek-ai/dsh-llm` 与 `@deepseek-ai/dsh-util-values`,以及只有对照断言会读其类型的 `@deepseek-ai/dsh-tool-executor-ptc`。profile 不装 peer,所以不会产生副本。
95
- - **版本要求把装载门与构建基线分开。** 每条 `@deepseek-ai/dsh-*` peer 与 `engines.dsh` 写同一条窗口,而这条窗口点明两条宿主线:caret 的预发布比较只吃自己那条 patch 元组,单写 `^0.2.1-rc.3` 收不进 `0.2.2-alpha.*`——用本工作区的 semver 实测 `satisfies('0.2.2-alpha.3','^0.2.1-rc.3')` 为 false,带 `{includePrerelease:true}` 才为 true,而宿主自己那道比较正是带这一档。代价是量出来写在这里的:npm 逐条 peer 独立解析,宿主家族每一枚包内部又把自己的那条线精确钉住(`0.2.1-rc` 线上的成员要求 cordis `~4.0.5`,`0.2.2-alpha.2` 宿主线要求 `~4.0.6-alpha.1`,`0.2.2-alpha.3` 要求 `~4.0.6-alpha.2`),所以消费者把宿主钉在旧那条线时仍可能止步 `ERESOLVE`,同一条树落在最新线上则可解。并集窗口也收得进同一 `0.2.2` 元组里更晚的预发布,例如 `0.2.2-rc.1`;那只是范围语义,不是兼容性取证。本版实际取证的基线仍是 `0.2.2-alpha.3`,要抬到 `rc.1` 必须同步移动 dev 钉版并重跑宿主对照断言。pnpm——本工作区支持、也是宿主 `dsh plugin add` 在用的安装器——两种形状都解得出,只打一条 peer 告警。装载门两边都不受影响。`devDependencies` 精确锁定本仓编译与测试用的那一线(`0.2.2-alpha.3`),且必须统一:一棵树里只有一份 `dsh-session`,nominal 类型才互相认得。`@deepseek-ai/cordis` 在 `devDependencies` 里精确锁定 `4.0.6-alpha.2`,peer 范围 `^4.0.5||^4.0.6-alpha.1` 同时覆盖 rc 线与 alpha 线——单独一枚包不存在宿主家族那种混线问题:alpha.2 要求 `~4.0.6-alpha.1`,alpha.3 要求 `~4.0.6-alpha.2`,该范围也收得下 `4.1.0`。cordis 这条并集在 `||` 两侧不带空格;类型面 peer 安装现已按行逐枚读取,dsh 家族窗口保留空格。`test/publish-manifest.test.ts` 分别检查这三件事。
96
+ - **版本要求把装载门与构建基线分开。** 每条 `@deepseek-ai/dsh-*` peer 与 `engines.dsh` 写同一条窗口,而这条窗口点明两条宿主线:caret 的预发布比较只吃自己那条 patch 元组,单写 `^0.2.1-rc.5` 收不进 `0.2.2-alpha.*`——用本工作区的 semver 实测 `satisfies('0.2.2-alpha.3','^0.2.1-rc.5')` 为 false,带 `{includePrerelease:true}` 才为 true,而宿主自己那道比较正是带这一档。代价是量出来写在这里的:npm 逐条 peer 独立解析,宿主家族每一枚包内部又把自己的那条线精确钉住(`0.2.1-rc` 线上任一成员要求 cordis `~4.0.5`,`0.2.2-alpha.2` 宿主要求 `~4.0.6-alpha.1`,`0.2.2-alpha.3` 要求 `~4.0.6-alpha.2`,`0.2.2-rc.1` 要求 `~4.1.0`),所以消费者把宿主钉在旧那条线时仍可能止步 `ERESOLVE`,同一条树落在最新线上则可解。并集窗口也收得进同一 `0.2.2` 元组里更晚的预发布,例如 `0.2.2-rc.1`;那只是范围语义,不是兼容性取证。本版实际取证的基线是 `0.2.2-rc.1`;dev 钉版与宿主对照断言已同步到这一线。pnpm——本工作区支持、也是宿主 `dsh plugin add` 在用的安装器——两种形状都解得出,只打一条 peer 告警。装载门两边都不受影响。`devDependencies` 精确锁定本仓编译与测试用的那一线(`0.2.2-rc.1`),且必须统一:一棵树里只有一份 `dsh-session`,nominal 类型才互相认得。`@deepseek-ai/cordis` 在 `devDependencies` 里精确锁定 `4.1.0`,peer 范围 `^4.0.5||^4.0.6-alpha.1` 同时覆盖 rc 线与 alpha 线——单独一枚包不存在宿主家族那种混线问题:alpha.2 要求 `~4.0.6-alpha.1`,alpha.3 要求 `~4.0.6-alpha.2`,该范围也收得下 `4.1.0`。cordis 这条并集在 `||` 两侧不带空格;类型面 peer 安装现已按行逐枚读取,dsh 家族窗口保留空格。`test/publish-manifest.test.ts` 分别检查这三件事。
96
97
  - 已知上游限制:`@deepseek-ai/dsh-llm` 的声明文件引用了它自己没声明的 `@deepseek-ai/dsh-attachment`。它经 `dsh-session` 传递进来,所以开 `skipLibCheck: false` 的消费方会看到来自那个包的 `TS2307`,不是来自本包。发布出去的 `./lib/tool-events` 声明还引用了 `@deepseek-ai/dsh-util-values`,它经 `dsh-session` / `dsh-tools` 传递进来;开 `skipLibCheck: false` 的消费方同样必须能解析到这个包。
97
98
 
98
99
  ### shared 的版本下限
@@ -117,8 +118,8 @@ import { isRecord, fieldOf } from "@jayyuen66/dsh-plugin-shared/lib/record";
117
118
 
118
119
  ## <img src="https://api.iconify.design/lucide/circle-slash.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> 它不提供什么
119
120
 
120
- - **不注册插件。** 没有 `dsh` 字段,没有 `cordis.patch.yml`,`--dump-config` 里没有它这一层;`dsh plugin add` 是错误动作。
121
- - **没有设置卡,也没有客户端部分。** 本包没有浏览器入口,也没有客户端状态。
121
+ - **不声明组合包。** 它为客户端行声明 `dsh.client`,但没有 `dsh.bundle.patch`;`node_modules` 里的副本本身不是 profile 层。客户端行由消费者自己的 patch 启用。
122
+ - **没有自己的设置卡。** 客户端行导出卡片组件;本包不注册卡片、不持有客户端状态,也没有路由。
122
123
  - **没有 HTTP 路由,也没有请求信任检查。** 真正注册路由的插件由宿主的 `/api` 网关负责认证、Host / Origin 校验与 body 上限。
123
124
  - **没有 CLI 子命令,也没有模型工具。**
124
125
  - **不做领域判定。** 它不知道哪些工具算编辑、哪条路径是密钥、预算多大、检查何时该报错。这些属于调用方的插件。
@@ -129,11 +130,13 @@ import { isRecord, fieldOf } from "@jayyuen66/dsh-plugin-shared/lib/record";
129
130
 
130
131
  | 子路径 | 提供什么 |
131
132
  | --- | --- |
132
- | `.` | 下面列出的运行期模块的 barrel。`canonicalize-region-paths` 刻意不进,运行期消费方不必为它增加一次导入;客户端专用的 `card-ui` 同样不进,理由还多一条:根 barrel 不得把 `react` 带进每个消费方 |
133
- | `./lib/tool-events` | `scanToolEvents` 返回 `calls` / `results` 两张表,PTC 子调用一并合并;`toolEventRowsOf` 处理单个事件,交回的记录已带归一后的 `arguments` 与 `badArguments`。`editPathOf` 返回按 `kind` 判别的 `EditTarget`,只有 `write` / `read-view` 两个分支;路径取不到时为 `undefined`。记录字段一律 `readonly`。同时再导出官方 `SessionEvent`;`PtcDispatchStartEventData` / `PtcDispatchEventData` 在本包内声明并自行增强 `SessionEventMap`,因为宿主在支持窗口内换过承载这份增强的包 |
133
+ | `.` | 下面列出的运行期模块的 barrel,由 `dist/plugin.js` 提供:同样的导出名,再加 `name` 与一条空操作的 `apply`,宿主 Loader 因此也能把本包当作一行载入。`canonicalize-region-paths` 刻意不进,运行期消费方不必为它增加一次导入;客户端专用的 `card-ui` 同样不进,理由还多一条:根 barrel 不得把 `react` 带进每个消费方 |
134
+ | `./lib/tool-events` | `scanToolEvents` 返回 `calls` / `results` 两张表,PTC 子调用一并合并;`toolEventRowsOf` 处理单个事件,交回的记录已带归一后的 `arguments` 与 `badArguments`。`editPathOf` 返回按 `kind` 判别的 `EditTarget`,只有 `write` / `read-view` 两个分支;路径取不到时为 `undefined`。`applyPatchTargetsOf` 用官方 `dsh-fs-patch` 解析 `apply_patch` 的有序操作;`editPathsOf` 展开全部写目标(move 的源与目的都算)。记录字段一律 `readonly`。同时再导出官方 `SessionEvent`;`PtcDispatchStartEventData` / `PtcDispatchEventData` 在本包内声明并自行增强 `SessionEventMap`,因为宿主在支持窗口内换过承载这份增强的包 |
134
135
  | `./lib/card-apply` | `claimApply` + `CardApplyCtx`:保证 apply 幂等的保护逻辑,建立在 `globalThis` 上的**自有**标记(symbol,或外部写入的 `true`)加 `ctx.effect` 清理。标记是进程级的,所以同一页面的第二个 ctx 会被拒绝——这是有意的设计 |
135
136
  | `./lib/card-diff` | `diffTouched`:touched 层里哪些字段真的与快照不同,按表单值比较。`undefined`、`null` 与键缺失是同一种值;数组与对象逐元素比较;循环引用也能正常结束 |
136
137
  | `./lib/card-ui` | 客户端专用设置卡组件:`CardForm`、`Section`、`FieldGrid`、`StatusBar`、`Field`、`FieldLabel`、`InfoTip`、`ToggleField`、`NumberField`、`TextField`、`ListField`、`SelectField`、`SaveBar`、`splitHint` 与样式字符串 `CARD_UI_CSS`。只用 `createElement`;`react` 是 optional peer,该子路径永不进根 barrel。只用 React 18 运行时 API(`useId`、`useState`、`useEffect`、refs) |
138
+ | `./client` | 浏览器客户端行:宿主模块图只提供一份的 `__ModuleLoader__` bundle。消费者外置这个说明符,并在自己的 patch 里以无 id 条目启用这一行 |
139
+ | `./client-node` | 同一客户端面的 Node/ESM 形态,供测试与类型感知的 Node 消费方使用 |
137
140
  | `./lib/project-key` | `deriveProjectKey` + `ProjectKeyOptions`:cwd → `尾目录名-<sha256(norm) 前 8 位 hex>`;非字符串、空串或归一后的根目录归入 `default`。`realpathSync` 会解析软链接,失败则退回解析后的路径。Windows 的 `\` 与 `/` 产生同一个键 |
138
141
  | `./lib/locale` | `Locale`(官方 `BuiltInLocaleId` 的别名)、`DEFAULT_LOCALE`、`resolveLocale`、`resolveLocalePreference`、`messagesFor`、`MessagesCatalog`,加 `LOCALE_SETTINGS_NAMESPACE` 与 `LOCALE_PREFERENCE_FIELD`。只负责宿主侧字典;客户端用官方 locale 包 |
139
142
  | `./lib/lesson-bus` | `settleLessonCall`:同步抛错与异步 rejection 归到同一个 `onFailure` 出口。函数型 thenable 也算可等待;`onFailure` 自己抛错或拒绝时被统一处理,不会分成"抛给调用方"或"未处理拒绝"两种结果 |
@@ -146,6 +149,8 @@ import { isRecord, fieldOf } from "@jayyuen66/dsh-plugin-shared/lib/record";
146
149
 
147
150
  `card-ui` 是唯一刻意只走子路径的运行期模块:把它加进 barrel 会让每个 host 侧 importer 都去解析 `react`。
148
151
 
152
+ 客户端行由消费插件启用,不由单独安装本包启用。消费者写 `- name: "@jayyuen66/dsh-plugin-shared"` 且**不写 id**;宿主照常装载它,插件页不会把它暴露成可单独停用的组件。写 id 会多出那个开关,关闭后所有需要该行组件的设置卡都会失效。
153
+
149
154
  ## <img src="https://api.iconify.design/lucide/sliders-horizontal.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> config 导出
150
155
 
151
156
  这些是给插件作者用的 lint/test 基线,不是运行期代码。它们依赖你的包自己提供的工具链:
@@ -153,7 +158,7 @@ import { isRecord, fieldOf } from "@jayyuen66/dsh-plugin-shared/lib/record";
153
158
  | 子路径 | 提供什么 | 你需要装什么 |
154
159
  | --- | --- | --- |
155
160
  | `./config/oxlint` | `definePluginConfig`、`makeTestFileOverrides`、`OFF_JUSTIFICATIONS` 与共享规则基线 | `oxlint`、`eslint-plugin-sonarjs`、`eslint-plugin-regexp`、`eslint-plugin-zod`(需 zod 4)、`@e18e/eslint-plugin` |
156
- | `./config/vitest.base` | `definePackageConfig`:四项 100% 覆盖率阈值、20 秒测试超时、共享覆盖率排除表与用例匹配范围 | `vitest` 与 `@vitest/coverage-v8`(基线把 `coverage.provider` 固定为 `"v8"`) |
161
+ | `./config/vitest.base` | `definePackageConfig`:四项 100% 覆盖率阈值、20 秒测试超时、共享覆盖率排除表、用例匹配范围,以及 `@jayyuen66/dsh-plugin-shared/client` → `@jayyuen66/dsh-plugin-shared/client-node` 的别名(UMD 行在装载时就读 `window`,Node 测试执行不了它) | `vitest` 与 `@vitest/coverage-v8`(基线把 `coverage.provider` 固定为 `"v8"`) |
157
162
  | `./config/tsconfig.base.json`、`./config/tsconfig.client.base.json` | 给 `extends` 用的纯 JSON | 什么都不用 |
158
163
 
159
164
  这些工具链包**不**写进 `peerDependencies`:npm 7+ 连 optional peer 都会去满足,而 `oxlint` 自己的 `peerOptional vite-plus` 把 `vitest` 锁定在一个与 `>=5.0.2` 无交集的版本上,结果是本包的普通 `npm install` 直接 `ERESOLVE` 失败。这条约束因此写在文档里,而不是 manifest 里。
@@ -203,7 +208,7 @@ import { isRecord, fieldOf } from "@jayyuen66/dsh-plugin-shared/lib/record";
203
208
 
204
209
  | 问题 | 回答 |
205
210
  | --- | --- |
206
- | 要不要 `dsh plugin add` 它? | 不要。它没有配置层也没有客户端部分,启用不了任何东西。正确做法是声明成插件包的依赖。 |
211
+ | 要不要 `dsh plugin add` 它? | 不要。它声明的是 `dsh.client`(消费者要启用的那一行),但没有 `dsh.bundle.patch`,单独添加不会装出配置层,也启用不了任何东西。正确做法是声明成插件包的依赖,再在自己的 `cordis.patch.yml` 里插入那条不带 id 的行。 |
207
212
  | 消费方启动时报 `does not provide an export named ...` 与 `N entries did not activate`。 | profile 提供的那份本包比消费方 import 的版本旧。把 profile 的 `overrides` 版本调高到包含该子路径的版本;对照本包的 `exports` 与 CHANGELOG。任何单仓 CI 都看不见这个状态。 |
208
213
  | 改了 `lib/*.ts`,消费方还是旧行为。 | 消费方载入的是 `dist/`。先运行 `pnpm run build`;`test/build-host.test.ts` 会在运行期产物过期时判定失败。workspace link 不会绕过产物。 |
209
214
  | 为什么 `@deepseek-ai/dsh-output-retention` 是必需 peer,其余都是 optional? | 它是唯一按值导入的宿主包(`truncateEnd`)。其余都是类型声明,唯一的例外是裸 `@deepseek-ai/dsh` peer——它只声明宿主版本线。 |
@@ -216,7 +221,7 @@ import { isRecord, fieldOf } from "@jayyuen66/dsh-plugin-shared/lib/record";
216
221
  pnpm install
217
222
  pnpm run check # typecheck → lint → build → test → 格式检查
218
223
  pnpm test # vitest,带覆盖率
219
- pnpm run build # 运行期产物、配置产物、声明文件
224
+ pnpm run build # 运行期产物、配置产物、声明文件、客户端行
220
225
  ```
221
226
 
222
227
  - 测试按「stub 覆盖不到的那类缺陷」分层:
@@ -1,2 +1,2 @@
1
- import{asRecord as e,closeQuietly as t,packageNameOf as n,readManifest as r,singleEntryChunk as i}from"./build-artifacts.js";import{t as a}from"./canonicalize-region-paths-DowoRhQK.js";import{readFile as o}from"node:fs/promises";import s from"node:path";const c=[`react`,`react/jsx-runtime`,`react-dom`,`react-dom/client`],l=/^\/\/(?:#region | )?(?:\.\.?\/)*node_modules\/\.pnpm\/[^/]+\/node_modules\//u,u=/node_modules\/\.pnpm\//u,d=/^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/u;function f(e){let t=e.replaceAll(/^\/\/(?:#region .*| .*)$/gmu,e=>e.replaceAll(`\\`,`/`).replace(l,e=>e.startsWith(`//#region`)?`//#region node_modules/`:`// node_modules/`)),n=u.exec(t);if(n!==null){let e=Math.max(0,n.index-40);throw Error(`产物里仍有 pnpm store 路径未折叠(出现在 …${JSON.stringify(t.slice(e,e+100))}…)。折叠判据与 rolldown 写下的形式不再匹配,client 产物的字节会随安装布局变化。`)}return t}async function p(e){return o(e,`utf8`)}function m(e){let t=e.name;if(typeof t!=`string`||t===``)throw Error(`package.json 的 name 缺失或非字符串。client 产物的 id 必须等于 dsh client 模块表的键(bundle 包名)。`);if(!d.test(t))throw Error(`package.json 的 name ${JSON.stringify(t)} 不符合 npm 包名字符集。`);return t}function h(e){let t=new Set(e);return e=>{if(t.has(e))return!0;let r=n(e);if(r===`react`||r===`react-dom`)throw Error(`${e} 是 react 家族说明符但不在外置清单里。外置会得到宿主模块表里没有的键(加载期报错),内联会得到第二份 React(hooks 静默失效);真要引入子路径,先把它加进外置清单并确认宿主模块表里有同名项。`);return!1}}async function g(n){let o=m(e(await r(n.root))),l=n.entry??s.join(`src`,`client-entry.ts`),u=n.minify??!1,d=n.comments??!u,g={format:`cjs`,...u?{minify:!0}:{},...d?{}:{comments:!1}},_=await n.rolldown({input:s.join(n.root,l),external:h(n.externals??c),platform:`browser`,cwd:n.root,resolve:{extensions:[`.tsx`,`.ts`,`.jsx`,`.js`,`.mjs`,`.json`]}}),v;try{let{output:e}=await _.generate(g),t=i(e,l);v=f(a(t.code.trimEnd(),n.root,process.cwd()))}catch(e){throw await t(_),e}return await _.close(),await n.verifyName?.({pkgName:o,body:v,readSource:e=>p(s.join(n.root,e))}),[n.header,`window.__ModuleLoader__.load({`,` id: ${JSON.stringify(o)},`,` factory: (require) => {`,` 'use strict';`,` const module = { exports: {} };`,` let exports = module.exports;`,v,` return module.exports;`,` },`,`});`,``].join(`
1
+ import{t as e}from"./canonicalize-region-paths-DowoRhQK.js";import{asRecord as t,closeQuietly as n,packageNameOf as r,readManifest as i,singleEntryChunk as a}from"./build-artifacts.js";import{readFile as o}from"node:fs/promises";import s from"node:path";const c=[`react`,`react/jsx-runtime`,`react-dom`,`react-dom/client`],l=/^\/\/(?:#region | )?(?:\.\.?\/)*node_modules\/\.pnpm\/[^/]+\/node_modules\//u,u=/node_modules\/\.pnpm\//u,d=/^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/u;function f(e){let t=e.replaceAll(/^\/\/(?:#region .*| .*)$/gmu,e=>e.replaceAll(`\\`,`/`).replace(l,e=>e.startsWith(`//#region`)?`//#region node_modules/`:`// node_modules/`)),n=u.exec(t);if(n!==null){let e=Math.max(0,n.index-40);throw Error(`产物里仍有 pnpm store 路径未折叠(出现在 …${JSON.stringify(t.slice(e,e+100))}…)。折叠判据与 rolldown 写下的形式不再匹配,client 产物的字节会随安装布局变化。`)}return t}async function p(e){return o(e,`utf8`)}function m(e){let t=e.name;if(typeof t!=`string`||t===``)throw Error(`package.json 的 name 缺失或非字符串。client 产物的 id 必须等于 dsh client 模块表的键(bundle 包名)。`);if(!d.test(t))throw Error(`package.json 的 name ${JSON.stringify(t)} 不符合 npm 包名字符集。`);return t}function h(e){let t=new Set(e);return e=>{if(t.has(e))return!0;let n=r(e);if(n===`react`||n===`react-dom`)throw Error(`${e} 是 react 家族说明符但不在外置清单里。外置会得到宿主模块表里没有的键(加载期报错),内联会得到第二份 React(hooks 静默失效);真要引入子路径,先把它加进外置清单并确认宿主模块表里有同名项。`);return!1}}async function g(r){let o=m(t(await i(r.root))),l=r.entry??s.join(`src`,`client-entry.ts`),u=r.minify??!1,d=r.comments??!u,g={format:`cjs`,...u?{minify:!0}:{},...d?{}:{comments:!1}},_=await r.rolldown({input:s.join(r.root,l),external:h(r.externals??c),platform:`browser`,cwd:r.root,resolve:{extensions:[`.tsx`,`.ts`,`.jsx`,`.js`,`.mjs`,`.json`]}}),v;try{let{output:t}=await _.generate(g),n=a(t,l);v=f(e(n.code.trimEnd(),r.root,process.cwd()))}catch(e){throw await n(_),e}return await _.close(),await r.verifyName?.({pkgName:o,body:v,readSource:e=>p(s.join(r.root,e))}),[r.header,`window.__ModuleLoader__.load({`,` id: ${JSON.stringify(o)},`,` factory: (require) => {`,` 'use strict';`,` const module = { exports: {} };`,` let exports = module.exports;`,v,` return module.exports;`,` },`,`});`,``].join(`
2
2
  `)}export{c as REACT_RUNTIME_MODULES,g as buildClientBundle,h as clientExternalPredicate,f as collapseStorePaths};
@@ -1 +1 @@
1
- import{asRecord as e,closeQuietly as t,isRecord as n,packageNameOf as r,readManifest as i,singleEntryChunk as a}from"./build-artifacts.js";import{t as o}from"./canonicalize-region-paths-DowoRhQK.js";import s from"node:path";function c(e,t){let r=e[t];if(r===void 0)return[];if(!n(r))throw TypeError(`package.json 的 ${t} 不是对象(实为 ${Array.isArray(r)?`array`:typeof r})。折成空集会让外部化名单失效、依赖全部内联进产物,所以拒绝继续。`);return Object.keys(r).map(e=>{if(typeof r[e]!=`string`)throw TypeError(`package.json 的 ${t}.${e} 不是字符串范围(收到 ${typeof r[e]})。静默跳过会让这个包被当成内部依赖整包内联,而内联恰好复制它的模块级状态。`);return e})}function l(e,t){let n=[...c(e,`dependencies`),...c(e,`peerDependencies`)];return t&&n.push(...c(e,`optionalDependencies`)),new Set(n)}function u(e){return Array.isArray(e)&&e.length>0&&e.every(e=>typeof e==`string`&&e.length>0)}async function d(n){let c=l(e(await i(n.root)),n.externalizeOptional??!0);if(c.size===0)throw Error(`package.json 的 dependencies / peerDependencies 都取不到字符串条目,外部化集合为空——依赖会被整包内联。`);let d=e=>c.has(r(e)),f=n.resolveExtensions??[`.ts`,`.mjs`,`.js`],p=n.minify??!1,m=n.comments??!p,h={format:`esm`,...p?{minify:!0}:{},...m?{}:{comments:!1}},g=async e=>{let r=await n.rolldown({input:s.join(n.root,e.source),external:d,platform:`node`,cwd:n.root,resolve:{extensions:[...f]},...n.plugins===void 0?{}:{plugins:n.plugins}}),i;try{let{output:t}=await r.generate(h),c=a(t,e.source);if(!u(c.moduleIds))throw TypeError(`rolldown 为 ${e.source} 产出的 entry chunk 的 moduleIds 不是"非空字符串数组"`);let l=o(c.code.trimEnd(),n.root,process.cwd());i={source:e.source,output:e.output,file:s.join(n.root,e.output),code:`${e.header}${l}\n`,inlinedModules:c.moduleIds}}catch(e){throw await t(r),e}return await r.close(),i};return Promise.all(n.entries.map(e=>g(e)))}export{d as buildHostEntries};
1
+ import{t as e}from"./canonicalize-region-paths-DowoRhQK.js";import{asRecord as t,closeQuietly as n,isRecord as r,packageNameOf as i,readManifest as a,singleEntryChunk as o}from"./build-artifacts.js";import s from"node:path";function c(e,t){let n=e[t];if(n===void 0)return[];if(!r(n))throw TypeError(`package.json 的 ${t} 不是对象(实为 ${Array.isArray(n)?`array`:typeof n})。折成空集会让外部化名单失效、依赖全部内联进产物,所以拒绝继续。`);return Object.keys(n).map(e=>{if(typeof n[e]!=`string`)throw TypeError(`package.json 的 ${t}.${e} 不是字符串范围(收到 ${typeof n[e]})。静默跳过会让这个包被当成内部依赖整包内联,而内联恰好复制它的模块级状态。`);return e})}function l(e,t){let n=[...c(e,`dependencies`),...c(e,`peerDependencies`)];return t&&n.push(...c(e,`optionalDependencies`)),new Set(n)}function u(e){return Array.isArray(e)&&e.length>0&&e.every(e=>typeof e==`string`&&e.length>0)}async function d(r){let c=l(t(await a(r.root)),r.externalizeOptional??!0);if(c.size===0)throw Error(`package.json 的 dependencies / peerDependencies 都取不到字符串条目,外部化集合为空——依赖会被整包内联。`);let d=e=>c.has(i(e)),f=r.resolveExtensions??[`.ts`,`.mjs`,`.js`],p=r.minify??!1,m=r.comments??!p,h={format:`esm`,...p?{minify:!0}:{},...m?{}:{comments:!1}},g=async t=>{let i=await r.rolldown({input:s.join(r.root,t.source),external:d,platform:`node`,cwd:r.root,resolve:{extensions:[...f]},...r.plugins===void 0?{}:{plugins:r.plugins}}),a;try{let{output:n}=await i.generate(h),c=o(n,t.source);if(!u(c.moduleIds))throw TypeError(`rolldown 为 ${t.source} 产出的 entry chunk 的 moduleIds 不是"非空字符串数组"`);let l=e(c.code.trimEnd(),r.root,process.cwd());a={source:t.source,output:t.output,file:s.join(r.root,t.output),code:`${t.header}${l}\n`,inlinedModules:c.moduleIds}}catch(e){throw await n(i),e}return await i.close(),a};return Promise.all(r.entries.map(e=>g(e)))}export{d as buildHostEntries};
@@ -0,0 +1,105 @@
1
+ import{createElement as e,useCallback as t,useEffect as n,useId as r,useLayoutEffect as i,useMemo as a,useRef as o,useState as s}from"react";const c=`
2
+ .dsu-form{display:flex;flex-direction:column}
3
+ .dsu-section{margin-top:24px;padding-top:24px;border-top:.5px solid var(--dsw-alias-border-l1)}
4
+ .dsu-form>.dsu-section:first-child{margin-top:0;padding-top:0;border-top:0}
5
+ .dsu-sectionTitle{margin:0;font-size:15px;font-weight:600;line-height:1.4;color:var(--dsw-alias-label-primary)}
6
+ .dsu-sectionHint{margin:4px 0 0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
7
+ .dsu-statusbar{display:flex;flex-wrap:wrap;gap:4px 8px;margin-top:8px;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
8
+ .dsu-field{display:grid;grid-template-columns:minmax(0,1fr) minmax(240px,360px);column-gap:32px;align-items:center;padding:12px 0;border-top:.5px solid var(--dsw-alias-border-l2)}
9
+ .dsu-fieldMeta{display:flex;flex-direction:column;gap:4px;min-width:0}
10
+ .dsu-labelRow{display:flex;align-items:center;gap:6px;min-width:0}
11
+ .dsu-label{font-size:13px;font-weight:500;line-height:1.5;color:var(--dsw-alias-label-primary)}
12
+ .dsu-labelMono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px}
13
+ .dsu-hint{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
14
+ .dsu-control{min-width:0;display:flex;align-items:center;gap:8px}
15
+ .dsu-fieldWide{grid-template-columns:minmax(0,1fr);align-items:start}
16
+ .dsu-fieldWide .dsu-control{width:100%;max-width:720px;margin-top:2px}
17
+ .dsu-help{display:inline-flex;align-items:center;gap:6px;min-width:0}
18
+ .dsu-helpBtn{appearance:none;border:0;background:transparent;padding:0;width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center;color:var(--dsw-alias-label-tertiary);cursor:help;border-radius:50%;flex:none}
19
+ .dsu-help:hover .dsu-helpBtn,.dsu-helpBtn:focus-visible{color:var(--dsw-alias-label-primary)}
20
+ .dsu-helpBtn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}
21
+ .dsu-tip{position:fixed;z-index:60;box-sizing:border-box;max-width:min(340px,calc(100vw - 16px));max-height:calc(100vh - 16px);overflow:auto;overflow-wrap:anywhere;padding:10px 12px;border-radius:10px;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l3);box-shadow:0 12px 32px rgb(0 0 0 / 45%);font-size:12px;line-height:1.6;font-weight:400;color:var(--dsw-alias-label-secondary)}
22
+ .dsu-input{width:100%;height:34px;padding:0 12px;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}
23
+ .dsu-input:focus-visible{outline:none;border-color:var(--dsw-alias-state-business-primary)}
24
+ .dsu-input[aria-invalid="true"]{border-color:var(--dsw-alias-state-error-primary)}
25
+ .dsu-input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
26
+ .dsu-input::placeholder{color:var(--dsw-alias-label-tertiary)}
27
+ .dsu-textarea{height:auto;min-height:96px;padding:8px 12px;line-height:1.6;resize:vertical}
28
+ .dsu-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
29
+ .dsu-numberWrap{display:flex;flex-direction:column;gap:4px;width:100%;min-width:0}
30
+ .dsu-number{display:flex;align-items:center;gap:8px;min-width:0}
31
+ .dsu-number .dsu-input{min-width:0}
32
+ .dsu-unit{flex:none;font-size:12px;color:var(--dsw-alias-label-tertiary)}
33
+ .dsu-error{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-state-error-primary)}
34
+ .dsu-switch{appearance:none;width:36px;height:20px;border-radius:999px;border:0;padding:2px;background:var(--dsw-alias-border-l3);display:flex;align-items:center;cursor:pointer;flex:none;transition:background-color .16s}
35
+ .dsu-knob{width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-label-primary);transition:transform .16s,background-color .16s}
36
+ .dsu-switch[aria-checked="true"]{background:var(--dsw-alias-label-primary)}
37
+ .dsu-switch[aria-checked="true"] .dsu-knob{transform:translateX(16px);background:var(--dsw-alias-label-primary-foreground)}
38
+ .dsu-switch:disabled{opacity:.4;cursor:not-allowed}
39
+ .dsu-switch:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}
40
+ .dsu-selectWrap{position:relative;width:100%;min-width:0}
41
+ .dsu-select{display:flex;align-items:center;justify-content:space-between;gap:8px;text-align:left;cursor:pointer}
42
+ .dsu-select[aria-expanded="true"]{border-color:var(--dsw-alias-state-business-primary)}
43
+ .dsu-selectValue{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
44
+ .dsu-chevron{flex:none;color:var(--dsw-alias-label-tertiary);transition:transform .16s}
45
+ .dsu-select[aria-expanded="true"] .dsu-chevron{transform:rotate(180deg)}
46
+ .dsu-menu{position:fixed;z-index:70;padding:4px;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l3);border-radius:12px;box-shadow:0 12px 32px rgb(0 0 0 / 50%);max-height:280px;overflow:auto}
47
+ .dsu-option{appearance:none;border:0;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:1.4;width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 10px;border-radius:8px;cursor:pointer;text-align:left}
48
+ .dsu-option:hover,.dsu-option[data-active="true"]{background:var(--dsw-alias-bg-layer-3)}
49
+ .dsu-option:disabled{color:var(--dsw-alias-label-tertiary);cursor:not-allowed}
50
+ .dsu-option:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}
51
+ .dsu-optionGroup{padding:6px 10px 2px;font-size:11px;font-weight:600;color:var(--dsw-alias-label-tertiary)}
52
+ .dsu-check{opacity:0;flex:none}
53
+ .dsu-option[aria-selected="true"] .dsu-check{opacity:1}
54
+ .dsu-savebar{display:flex;align-items:center;gap:8px;padding:12px 0;margin-top:24px;border-top:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base)}
55
+ .dsu-savebar[data-dirty="true"]{position:sticky;bottom:0;z-index:2;box-shadow:0 -12px 24px rgb(0 0 0 / 35%)}
56
+ .dsu-btn{appearance:none;border:.5px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:1.5;border-radius:var(--dsw-radius-md);padding:5px 14px;cursor:pointer;transition:background-color .12s,border-color .12s,color .12s,filter .12s}
57
+ .dsu-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l3)}
58
+ .dsu-btn:active:not(:disabled){background:var(--dsw-alias-bg-layer-1)}
59
+ .dsu-btn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}
60
+ .dsu-btn:disabled{opacity:.4;cursor:not-allowed}
61
+ .dsu-btnPrimary{background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}
62
+ .dsu-btnPrimary:hover:not(:disabled){background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);filter:brightness(.9)}
63
+ .dsu-btnPrimary:active:not(:disabled){background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);filter:brightness(.84)}
64
+ .dsu-btnDanger{color:var(--dsw-alias-state-error-primary)}
65
+ .dsu-btnDanger:hover:not(:disabled){color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-2)}
66
+ .dsu-status{margin-left:auto;font-size:12px;color:var(--dsw-alias-label-tertiary)}
67
+ .dsu-statusError{color:var(--dsw-alias-state-error-primary)}
68
+ .dsu-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(200px,100%),1fr));gap:0 24px}
69
+ .dsu-grid>.dsu-field{display:flex;flex-direction:column;align-items:stretch;gap:6px;padding:10px 0;border-top:.5px solid var(--dsw-alias-border-l1)}
70
+ .dsu-grid>.dsu-field .dsu-control{width:100%}
71
+ @media (max-width:640px){
72
+ .dsu-field{grid-template-columns:minmax(0,1fr);align-items:start}
73
+ .dsu-control{width:100%;max-width:720px}
74
+ .dsu-section{margin-top:20px;padding-top:20px}
75
+ }
76
+ @media (prefers-reduced-motion:reduce){
77
+ .dsu-switch,.dsu-knob,.dsu-chevron,.dsu-btn{transition:none}
78
+ }
79
+ `;function l(e,t=30){return e.length<=t?{hint:e}:{detail:e}}function u(t){return e(`div`,{className:`dsu-form`},t.children)}function d(t){return e(`section`,{className:`dsu-section`},e(`h2`,{className:`dsu-sectionTitle`},t.title),t.hint===void 0?null:e(`p`,{className:`dsu-sectionHint`},t.hint),t.children)}function f(t){return e(`div`,{className:`dsu-grid`},t.children)}function p(t){if(t.items.length===0)return null;let n=t.items.map((t,n)=>e(`span`,{key:n},n===0?t:`· ${t}`));return e(`div`,{className:`dsu-statusbar`},...n)}function m(a){let c=r(),[l,u]=s(!1),[d,f]=s(!1),p=o(!1),m=o(!1),h=o(!1),g=o(null),_=o(null),v=o(null),y=t(()=>{v.current!==null&&(clearTimeout(v.current),v.current=null)},[]),b=t(()=>{let e=g.current,t=_.current;
80
+ /* v8 ignore next 3 -- place 只在 open 的 layout effect 与挂载期监听器里调用;refs 为空只可能是卸载竞态 */
81
+ if(e===null||t===null)return;let n=e.getBoundingClientRect(),r=t.offsetWidth,i=t.offsetHeight,{left:a}=n,o=n.bottom+8;o+i>window.innerHeight-8&&(o=n.top-i-8),a+r>window.innerWidth-8&&(a=window.innerWidth-r-8);let s=Math.max(8,window.innerWidth-r-8),c=Math.max(8,window.innerHeight-i-8);t.style.left=`${Math.min(Math.max(8,a),s)}px`,t.style.top=`${Math.min(Math.max(8,o),c)}px`},[]),x=t(()=>{y(),h.current=!1,f(!1),u(!1)},[y]),S=t(()=>{y(),u(!0)},[y]),C=t(()=>{y(),p.current||m.current||h.current||(v.current=setTimeout(()=>{
82
+ /* v8 ignore next 3 -- 任何重新进入/钉住都会 cancelHide;这次复查只防未来的异步路径 */
83
+ v.current=null,p.current||m.current||h.current||u(!1)},120))},[y]),w=t(e=>{h.current=e,f(e)},[]);i(()=>{if(!l)return()=>{};b();let e=e=>{let{target:t}=e;t instanceof Node&&_.current?.contains(t)===!0||(h.current?b():x())},t=()=>{b()};return window.addEventListener(`scroll`,e,!0),window.addEventListener(`resize`,t),()=>{window.removeEventListener(`scroll`,e,!0),window.removeEventListener(`resize`,t)}},[l,a.detail,b,x]),n(()=>{if(!l)return()=>{};let e=e=>{e.key===`Escape`&&x()};return document.addEventListener(`keydown`,e),()=>{document.removeEventListener(`keydown`,e)}},[l,x]),n(()=>y,[y]);let T=e(`button`,{ref:g,type:`button`,className:`dsu-helpBtn`,"aria-label":`${a.label} 说明`,"aria-describedby":c,"aria-pressed":d,onClick:()=>{h.current?x():(w(!0),S())}},e(`svg`,{width:12,height:12,viewBox:`0 0 14 14`,"aria-hidden":!0},e(`circle`,{cx:7,cy:7,r:6,fill:`none`,stroke:`currentColor`,strokeWidth:1.2}),e(`path`,{d:`M7 6.2v4`,stroke:`currentColor`,strokeWidth:1.3,strokeLinecap:`round`}),e(`circle`,{cx:7,cy:4.3,r:.8,fill:`currentColor`}))),E=e(`div`,{ref:_,id:c,role:`tooltip`,className:`dsu-tip`,hidden:!l},a.detail),D=a.showLabel===!1?null:e(`span`,{className:`dsu-label${a.compact===!0?` dsu-labelMono`:``}`},a.label);return e(`span`,{className:`dsu-help`,onMouseEnter:()=>{p.current=!0,S()},onMouseLeave:()=>{p.current=!1,C()},onFocus:()=>{m.current=!0,S()},onBlur:e=>{let t=e.relatedTarget;t instanceof Node&&e.currentTarget.contains(t)||(m.current=!1,C())}},D,T,E)}function h(t){let n=`dsu-label${t.compact===!0?` dsu-labelMono`:``}`,r=t.controlId===void 0?e(`span`,{className:n},t.label):e(`label`,{className:n,htmlFor:t.controlId},t.label);return e(`div`,{className:`dsu-labelRow`},r,t.detail===void 0?null:e(m,{label:t.label,detail:t.detail,showLabel:!1}))}function g(t){let n=t.controlId===void 0?void 0:`${t.controlId}-hint`;return e(`div`,{className:`dsu-field${t.wide===!0?` dsu-fieldWide`:``}`},e(`div`,{className:`dsu-fieldMeta`},e(h,{label:t.label,detail:t.detail,compact:t.compact,controlId:t.controlId}),t.hint===void 0?null:e(`p`,{className:`dsu-hint`,id:n},t.hint)),e(`div`,{className:`dsu-control`},t.children))}function _(t){let n=r(),i=t.hint===void 0?void 0:`${n}-hint`;return e(g,{...t,controlId:n},e(`button`,{type:`button`,role:`switch`,id:n,className:`dsu-switch`,"aria-checked":t.checked,"aria-describedby":i,"data-field":t.field,disabled:t.disabled===!0,onClick:()=>{t.onChange(!t.checked)}},e(`span`,{className:`dsu-knob`})))}function v(t){let i=typeof t.value==`number`&&Number.isFinite(t.value)?String(t.value):``,[a,c]=s(i),[l,u]=s(!1),d=o(void 0),f=r(),p=r(),m=[t.hint===void 0?void 0:`${f}-hint`,l?p:void 0].filter(e=>e!==void 0),h=m.length===0?void 0:m.join(` `);n(()=>{t.value!==d.current&&(d.current=t.value,c(i),u(!1))},[t.value,i]);let _=e=>{let n=t.min??-1/0,r=t.max??1/0;if(!Number.isFinite(e)||e<n||e>r)return!1;let{step:i}=t;if(i===void 0||i<=0)return!0;let a=(e-(t.min??0))/i;return Math.abs(a-Math.round(a))<1e-9},v=e=>{if(c(e),e===``){u(!1),d.current=void 0,t.onChange(void 0);return}let n=Number(e);_(n)?(u(!1),d.current=n,t.onChange(n)):u(!0)},y=()=>{a===``||l||c(String(Number(a)))};return e(g,{...t,controlId:f},e(`div`,{className:`dsu-numberWrap`},e(`div`,{className:`dsu-number`},e(`input`,{className:`dsu-input`,type:`number`,inputMode:`numeric`,id:f,value:a,min:t.min,max:t.max,step:t.step,placeholder:t.placeholder,"aria-invalid":l||void 0,"aria-describedby":h,"data-field":t.field,disabled:t.disabled===!0,onChange:e=>{v(e.target.value)},onBlur:y}),t.unit===void 0?null:e(`span`,{className:`dsu-unit`},t.unit)),l?e(`p`,{className:`dsu-error`,id:p},t.invalidHint):null))}function y(t){let[i,a]=s(t.value),[o,c]=s(!1),l=r(),u=t.hint===void 0?void 0:`${l}-hint`;n(()=>{o||a(t.value)},[t.value,o]);let d={id:l,className:`dsu-input${t.multiline===!0?` dsu-textarea`:``}${t.monospace===!0?` dsu-mono`:``}`,value:i,placeholder:t.placeholder,"aria-describedby":u,"data-field":t.field,disabled:t.disabled===!0,onFocus:()=>{c(!0)},onBlur:()=>{c(!1)},onChange:e=>{a(e.target.value),t.onChange(e.target.value)}};return e(g,{...t,controlId:l},t.multiline===!0?e(`textarea`,d):e(`input`,d))}function b(t){let i=t.separator??`comma`,a=t.value.join(i===`newline`?`
84
+ `:`, `),[o,c]=s(a),[l,u]=s(!1),d=r(),f=t.hint===void 0?void 0:`${d}-hint`;n(()=>{l||c(a)},[a,l]);let p=e=>e.split(i===`newline`?`
85
+ `:`,`).map(e=>e.trim()).filter(e=>e.length>0),m={id:d,className:`dsu-input${i===`newline`?` dsu-textarea dsu-mono`:``}`,value:o,placeholder:t.placeholder,"aria-describedby":f,"data-field":t.field,disabled:t.disabled===!0,onFocus:()=>{u(!0)},onBlur:()=>{u(!1)},onChange:e=>{c(e.target.value),t.onChange(p(e.target.value))}};return e(g,{...t,controlId:d},e(i===`newline`?`textarea`:`input`,m))}function x(c){let[l,u]=s(!1),[d,f]=s(0),p=o(null),m=o(null),h=r(),_=r(),v=c.hint===void 0?void 0:`${_}-hint`,{disabled:y,value:b,options:x,onChange:S}=c,C=a(()=>x.map((e,t)=>({option:e,index:t})).filter(e=>e.option.disabled!==!0),[x]),w=x.find(e=>e.value===b)?.label??b,T=x[d],E=T!==void 0&&T.disabled!==!0,D=t(()=>{let e=p.current,t=m.current;
86
+ /* v8 ignore next 3 -- place 只在 open 的 layout effect 里调用;refs 为空只可能是卸载竞态 */
87
+ if(e===null||t===null)return;let n=e.getBoundingClientRect(),r=Math.min(n.width,window.innerWidth-16),{left:i}=n;i+r>window.innerWidth-8&&(i=window.innerWidth-r-8);let a=t.offsetHeight,o=n.bottom+6;o+a>window.innerHeight-8&&(o=n.top-a-6);let s=Math.max(8,window.innerWidth-r-8),c=Math.max(8,window.innerHeight-a-8);t.style.width=`${r}px`,t.style.left=`${Math.min(Math.max(8,i),s)}px`,t.style.top=`${Math.min(Math.max(8,o),c)}px`},[]),O=t(()=>{u(!1)},[]),k=t(()=>{if(y===!0)return;let e=C.find(e=>e.option.value===b)??C.at(0);e!==void 0&&(f(e.index),u(!0))},[y,b,C]),A=t(e=>{
88
+ /* v8 ignore next 3 -- 浮层只能经由 openMenu 打开;组件禁用时打不开 */
89
+ if(y===!0)return;let t=x[e];
90
+ /* v8 ignore next 3 -- disabled 选项在浏览器里不派发 click;越界下标由调用面保证 */
91
+ t!==void 0&&t.disabled!==!0&&(S(t.value),O(),p.current?.focus())},[y,x,S,O]),j=t(e=>{let t=C.findIndex(e=>e.index===d);
92
+ /* v8 ignore next 5 -- open 时归一 effect 已保证 enabled 非空且 active 落在可用项上 */
93
+ if(t===-1||C.length===0)return;let n=(t+e+C.length)%C.length,r=C[n];
94
+ /* v8 ignore next 3 -- next 由取模保证在界内 */
95
+ r!==void 0&&f(r.index)},[d,C]);i(()=>{if(!l)return()=>{};D();let e=e=>{let t=e.target;m.current?.contains(t)!==!0&&p.current?.contains(t)!==!0&&O()},t=e=>{let{target:t}=e;t instanceof Node&&m.current?.contains(t)===!0||O()},n=()=>{D()};return document.addEventListener(`pointerdown`,e,!0),window.addEventListener(`scroll`,t,!0),window.addEventListener(`resize`,n),()=>{document.removeEventListener(`pointerdown`,e,!0),window.removeEventListener(`scroll`,t,!0),window.removeEventListener(`resize`,n)}},[l,c.options,D,O]),n(()=>{if(!l)return;if(y===!0||C.length===0){O();return}if(C.some(e=>e.index===d))return;let e=C.find(e=>e.option.value===b)??C.at(0);
96
+ /* v8 ignore next 3 -- enabled 非空在上一分支已保证 */
97
+ e!==void 0&&f(e.index)},[l,y,b,x,d,O,C]),i(()=>{if(!l)return;let e=m.current;
98
+ /* v8 ignore next 2 -- 同上,menu 在 open 的 commit 之后必然已挂上 */
99
+ if(e===null)return;let t=e.querySelector(`[data-active="true"]`);
100
+ /* v8 ignore next 2 -- active 已由归一 effect 保证落在可用项上 */
101
+ t!==null&&(t.offsetTop<e.scrollTop?e.scrollTop=t.offsetTop:t.offsetTop+t.offsetHeight>e.scrollTop+e.clientHeight&&(e.scrollTop=t.offsetTop+t.offsetHeight-e.clientHeight))},[l,d,x]);let M=e=>{if(e.key===`ArrowDown`||e.key===`ArrowUp`){if(e.preventDefault(),!l){k();return}j(e.key===`ArrowDown`?1:-1)}else if(e.key===`Home`||e.key===`End`){if(e.preventDefault(),l||k(),C.length===0)return;let t=e.key===`Home`?C.at(0):C.at(-1);
102
+ /* v8 ignore next 3 -- enabled 为空已在上一条返回 */
103
+ if(t===void 0)return;f(t.index)}else if(e.key===`Enter`||e.key===` `){if(e.preventDefault(),!l){k();return}A(d)}else(e.key===`Tab`||e.key===`Escape`)&&O()},N=[],P;for(let[t,n]of x.entries())n.group!==P&&(n.group!==void 0&&N.push(e(`div`,{key:`group-${t}-${n.group}`,className:`dsu-optionGroup`,role:`presentation`},n.group)),P=n.group),N.push(e(`button`,{key:`option-${n.value}`,type:`button`,role:`option`,id:`${h}-${t}`,className:`dsu-option`,tabIndex:-1,"aria-selected":n.value===c.value,"data-active":t===d,disabled:n.disabled===!0,onMouseDown:e=>{e.preventDefault()},onClick:()=>{A(t)},onMouseEnter:()=>{
104
+ /* v8 ignore next -- happy-dom 对 disabled 元素不派发 mouse 事件;浏览器实现各有差异,保留禁用检查 */
105
+ n.disabled!==!0&&f(t)}},e(`span`,null,n.label),e(`svg`,{className:`dsu-check`,width:14,height:14,viewBox:`0 0 14 14`,"aria-hidden":!0},e(`path`,{d:`M2.5 7.5l3 3 6-7`,fill:`none`,stroke:`currentColor`,strokeWidth:1.5,strokeLinecap:`round`,strokeLinejoin:`round`}))));return e(g,{...c,controlId:_},e(`div`,{className:`dsu-selectWrap`,onBlur:e=>{let t=e.relatedTarget;t instanceof Node&&e.currentTarget.contains(t)||O()}},e(`button`,{ref:p,type:`button`,id:_,className:`dsu-select dsu-input`,role:`combobox`,"aria-haspopup":`listbox`,"aria-expanded":l,"aria-controls":`${h}-list`,"aria-activedescendant":l&&E?`${h}-${d}`:void 0,"aria-describedby":v,"data-field":c.field,disabled:y===!0||x.length===0,onClick:()=>{l?O():k()},onKeyDown:M},e(`span`,{className:`dsu-selectValue`},w),e(`svg`,{className:`dsu-chevron`,width:14,height:14,viewBox:`0 0 14 14`,"aria-hidden":!0},e(`path`,{d:`M3 5l4 4 4-4`,fill:`none`,stroke:`currentColor`,strokeWidth:1.5,strokeLinecap:`round`,strokeLinejoin:`round`}))),e(`div`,{ref:m,id:`${h}-list`,role:`listbox`,className:`dsu-menu`,"aria-label":c.label,hidden:!l},...N)))}function S(t){let n=!t.writable||t.busy||!t.dirty,r;return r=t.error===null?t.writable?t.dirty?t.dirtyLabel:t.cleanLabel:t.readOnlyLabel:t.error,e(`div`,{className:`dsu-savebar`,"data-dirty":t.dirty},e(`button`,{type:`button`,className:`dsu-btn dsu-btnPrimary`,"data-field":`save`,disabled:n,onClick:t.onSave},t.busy?t.savingLabel:t.saveLabel),e(`button`,{type:`button`,className:`dsu-btn`,"data-field":`discard`,disabled:n,onClick:t.onDiscard},t.revertLabel),e(`span`,{className:t.error===null?`dsu-status`:`dsu-status dsu-statusError`},r))}export{y as a,h as c,f as d,d as f,c as h,v as i,m as l,l as m,x as n,_ as o,p,b as r,g as s,S as t,u};
package/dist/card-ui.js CHANGED
@@ -1,105 +1 @@
1
- import{createElement as e,useCallback as t,useEffect as n,useId as r,useLayoutEffect as i,useMemo as a,useRef as o,useState as s}from"react";const c=`
2
- .dsu-form{display:flex;flex-direction:column}
3
- .dsu-section{margin-top:24px;padding-top:24px;border-top:.5px solid var(--dsw-alias-border-l1)}
4
- .dsu-form>.dsu-section:first-child{margin-top:0;padding-top:0;border-top:0}
5
- .dsu-sectionTitle{margin:0;font-size:15px;font-weight:600;line-height:1.4;color:var(--dsw-alias-label-primary)}
6
- .dsu-sectionHint{margin:4px 0 0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
7
- .dsu-statusbar{display:flex;flex-wrap:wrap;gap:4px 8px;margin-top:8px;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
8
- .dsu-field{display:grid;grid-template-columns:minmax(0,1fr) minmax(240px,360px);column-gap:32px;align-items:center;padding:12px 0;border-top:.5px solid var(--dsw-alias-border-l2)}
9
- .dsu-fieldMeta{display:flex;flex-direction:column;gap:4px;min-width:0}
10
- .dsu-labelRow{display:flex;align-items:center;gap:6px;min-width:0}
11
- .dsu-label{font-size:13px;font-weight:500;line-height:1.5;color:var(--dsw-alias-label-primary)}
12
- .dsu-labelMono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px}
13
- .dsu-hint{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
14
- .dsu-control{min-width:0;display:flex;align-items:center;gap:8px}
15
- .dsu-fieldWide{grid-template-columns:minmax(0,1fr);align-items:start}
16
- .dsu-fieldWide .dsu-control{width:100%;max-width:720px;margin-top:2px}
17
- .dsu-help{display:inline-flex;align-items:center;gap:6px;min-width:0}
18
- .dsu-helpBtn{appearance:none;border:0;background:transparent;padding:0;width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center;color:var(--dsw-alias-label-tertiary);cursor:help;border-radius:50%;flex:none}
19
- .dsu-help:hover .dsu-helpBtn,.dsu-helpBtn:focus-visible{color:var(--dsw-alias-label-primary)}
20
- .dsu-helpBtn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}
21
- .dsu-tip{position:fixed;z-index:60;box-sizing:border-box;max-width:min(340px,calc(100vw - 16px));max-height:calc(100vh - 16px);overflow:auto;overflow-wrap:anywhere;padding:10px 12px;border-radius:10px;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l3);box-shadow:0 12px 32px rgb(0 0 0 / 45%);font-size:12px;line-height:1.6;font-weight:400;color:var(--dsw-alias-label-secondary)}
22
- .dsu-input{width:100%;height:34px;padding:0 12px;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}
23
- .dsu-input:focus-visible{outline:none;border-color:var(--dsw-alias-state-business-primary)}
24
- .dsu-input[aria-invalid="true"]{border-color:var(--dsw-alias-state-error-primary)}
25
- .dsu-input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
26
- .dsu-input::placeholder{color:var(--dsw-alias-label-tertiary)}
27
- .dsu-textarea{height:auto;min-height:96px;padding:8px 12px;line-height:1.6;resize:vertical}
28
- .dsu-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
29
- .dsu-numberWrap{display:flex;flex-direction:column;gap:4px;width:100%;min-width:0}
30
- .dsu-number{display:flex;align-items:center;gap:8px;min-width:0}
31
- .dsu-number .dsu-input{min-width:0}
32
- .dsu-unit{flex:none;font-size:12px;color:var(--dsw-alias-label-tertiary)}
33
- .dsu-error{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-state-error-primary)}
34
- .dsu-switch{appearance:none;width:36px;height:20px;border-radius:999px;border:0;padding:2px;background:var(--dsw-alias-border-l3);display:flex;align-items:center;cursor:pointer;flex:none;transition:background-color .16s}
35
- .dsu-knob{width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-label-primary);transition:transform .16s,background-color .16s}
36
- .dsu-switch[aria-checked="true"]{background:var(--dsw-alias-label-primary)}
37
- .dsu-switch[aria-checked="true"] .dsu-knob{transform:translateX(16px);background:var(--dsw-alias-label-primary-foreground)}
38
- .dsu-switch:disabled{opacity:.4;cursor:not-allowed}
39
- .dsu-switch:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}
40
- .dsu-selectWrap{position:relative;width:100%;min-width:0}
41
- .dsu-select{display:flex;align-items:center;justify-content:space-between;gap:8px;text-align:left;cursor:pointer}
42
- .dsu-select[aria-expanded="true"]{border-color:var(--dsw-alias-state-business-primary)}
43
- .dsu-selectValue{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
44
- .dsu-chevron{flex:none;color:var(--dsw-alias-label-tertiary);transition:transform .16s}
45
- .dsu-select[aria-expanded="true"] .dsu-chevron{transform:rotate(180deg)}
46
- .dsu-menu{position:fixed;z-index:70;padding:4px;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l3);border-radius:12px;box-shadow:0 12px 32px rgb(0 0 0 / 50%);max-height:280px;overflow:auto}
47
- .dsu-option{appearance:none;border:0;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:1.4;width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 10px;border-radius:8px;cursor:pointer;text-align:left}
48
- .dsu-option:hover,.dsu-option[data-active="true"]{background:var(--dsw-alias-bg-layer-3)}
49
- .dsu-option:disabled{color:var(--dsw-alias-label-tertiary);cursor:not-allowed}
50
- .dsu-option:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}
51
- .dsu-optionGroup{padding:6px 10px 2px;font-size:11px;font-weight:600;color:var(--dsw-alias-label-tertiary)}
52
- .dsu-check{opacity:0;flex:none}
53
- .dsu-option[aria-selected="true"] .dsu-check{opacity:1}
54
- .dsu-savebar{display:flex;align-items:center;gap:8px;padding:12px 0;margin-top:24px;border-top:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base)}
55
- .dsu-savebar[data-dirty="true"]{position:sticky;bottom:0;z-index:2;box-shadow:0 -12px 24px rgb(0 0 0 / 35%)}
56
- .dsu-btn{appearance:none;border:.5px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:1.5;border-radius:var(--dsw-radius-md);padding:5px 14px;cursor:pointer;transition:background-color .12s,border-color .12s,color .12s,filter .12s}
57
- .dsu-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l3)}
58
- .dsu-btn:active:not(:disabled){background:var(--dsw-alias-bg-layer-1)}
59
- .dsu-btn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}
60
- .dsu-btn:disabled{opacity:.4;cursor:not-allowed}
61
- .dsu-btnPrimary{background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}
62
- .dsu-btnPrimary:hover:not(:disabled){background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);filter:brightness(.9)}
63
- .dsu-btnPrimary:active:not(:disabled){background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);filter:brightness(.84)}
64
- .dsu-btnDanger{color:var(--dsw-alias-state-error-primary)}
65
- .dsu-btnDanger:hover:not(:disabled){color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-2)}
66
- .dsu-status{margin-left:auto;font-size:12px;color:var(--dsw-alias-label-tertiary)}
67
- .dsu-statusError{color:var(--dsw-alias-state-error-primary)}
68
- .dsu-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(200px,100%),1fr));gap:0 24px}
69
- .dsu-grid>.dsu-field{display:flex;flex-direction:column;align-items:stretch;gap:6px;padding:10px 0;border-top:.5px solid var(--dsw-alias-border-l1)}
70
- .dsu-grid>.dsu-field .dsu-control{width:100%}
71
- @media (max-width:640px){
72
- .dsu-field{grid-template-columns:minmax(0,1fr);align-items:start}
73
- .dsu-control{width:100%;max-width:720px}
74
- .dsu-section{margin-top:20px;padding-top:20px}
75
- }
76
- @media (prefers-reduced-motion:reduce){
77
- .dsu-switch,.dsu-knob,.dsu-chevron,.dsu-btn{transition:none}
78
- }
79
- `;function l(e,t=30){return e.length<=t?{hint:e}:{detail:e}}function u(t){return e(`div`,{className:`dsu-form`},t.children)}function d(t){return e(`section`,{className:`dsu-section`},e(`h2`,{className:`dsu-sectionTitle`},t.title),t.hint===void 0?null:e(`p`,{className:`dsu-sectionHint`},t.hint),t.children)}function f(t){return e(`div`,{className:`dsu-grid`},t.children)}function p(t){if(t.items.length===0)return null;let n=t.items.map((t,n)=>e(`span`,{key:n},n===0?t:`· ${t}`));return e(`div`,{className:`dsu-statusbar`},...n)}function m(a){let c=r(),[l,u]=s(!1),[d,f]=s(!1),p=o(!1),m=o(!1),h=o(!1),g=o(null),_=o(null),v=o(null),y=t(()=>{v.current!==null&&(clearTimeout(v.current),v.current=null)},[]),b=t(()=>{let e=g.current,t=_.current;
80
- /* v8 ignore next 3 -- place 只在 open 的 layout effect 与挂载期监听器里调用;refs 为空只可能是卸载竞态 */
81
- if(e===null||t===null)return;let n=e.getBoundingClientRect(),r=t.offsetWidth,i=t.offsetHeight,{left:a}=n,o=n.bottom+8;o+i>window.innerHeight-8&&(o=n.top-i-8),a+r>window.innerWidth-8&&(a=window.innerWidth-r-8);let s=Math.max(8,window.innerWidth-r-8),c=Math.max(8,window.innerHeight-i-8);t.style.left=`${Math.min(Math.max(8,a),s)}px`,t.style.top=`${Math.min(Math.max(8,o),c)}px`},[]),x=t(()=>{y(),h.current=!1,f(!1),u(!1)},[y]),S=t(()=>{y(),u(!0)},[y]),C=t(()=>{y(),!(p.current||m.current||h.current)&&(v.current=setTimeout(()=>{
82
- /* v8 ignore next 3 -- 任何重新进入/钉住都会 cancelHide;这次复查只防未来的异步路径 */
83
- v.current=null,p.current||m.current||h.current||u(!1)},120))},[y]),w=t(e=>{h.current=e,f(e)},[]);i(()=>{if(!l)return()=>{};b();let e=e=>{let{target:t}=e;t instanceof Node&&_.current?.contains(t)===!0||(h.current?b():x())},t=()=>{b()};return window.addEventListener(`scroll`,e,!0),window.addEventListener(`resize`,t),()=>{window.removeEventListener(`scroll`,e,!0),window.removeEventListener(`resize`,t)}},[l,a.detail,b,x]),n(()=>{if(!l)return()=>{};let e=e=>{e.key===`Escape`&&x()};return document.addEventListener(`keydown`,e),()=>{document.removeEventListener(`keydown`,e)}},[l,x]),n(()=>y,[y]);let T=e(`button`,{ref:g,type:`button`,className:`dsu-helpBtn`,"aria-label":`${a.label} 说明`,"aria-describedby":c,"aria-pressed":d,onClick:()=>{h.current?x():(w(!0),S())}},e(`svg`,{width:12,height:12,viewBox:`0 0 14 14`,"aria-hidden":!0},e(`circle`,{cx:7,cy:7,r:6,fill:`none`,stroke:`currentColor`,strokeWidth:1.2}),e(`path`,{d:`M7 6.2v4`,stroke:`currentColor`,strokeWidth:1.3,strokeLinecap:`round`}),e(`circle`,{cx:7,cy:4.3,r:.8,fill:`currentColor`}))),E=e(`div`,{ref:_,id:c,role:`tooltip`,className:`dsu-tip`,hidden:!l},a.detail),D=a.showLabel===!1?null:e(`span`,{className:`dsu-label${a.compact===!0?` dsu-labelMono`:``}`},a.label);return e(`span`,{className:`dsu-help`,onMouseEnter:()=>{p.current=!0,S()},onMouseLeave:()=>{p.current=!1,C()},onFocus:()=>{m.current=!0,S()},onBlur:e=>{let t=e.relatedTarget;t instanceof Node&&e.currentTarget.contains(t)||(m.current=!1,C())}},D,T,E)}function h(t){let n=`dsu-label${t.compact===!0?` dsu-labelMono`:``}`,r=t.controlId===void 0?e(`span`,{className:n},t.label):e(`label`,{className:n,htmlFor:t.controlId},t.label);return e(`div`,{className:`dsu-labelRow`},r,t.detail===void 0?null:e(m,{label:t.label,detail:t.detail,showLabel:!1}))}function g(t){let n=t.controlId===void 0?void 0:`${t.controlId}-hint`;return e(`div`,{className:`dsu-field${t.wide===!0?` dsu-fieldWide`:``}`},e(`div`,{className:`dsu-fieldMeta`},e(h,{label:t.label,detail:t.detail,compact:t.compact,controlId:t.controlId}),t.hint===void 0?null:e(`p`,{className:`dsu-hint`,id:n},t.hint)),e(`div`,{className:`dsu-control`},t.children))}function _(t){let n=r(),i=t.hint===void 0?void 0:`${n}-hint`;return e(g,{...t,controlId:n},e(`button`,{type:`button`,role:`switch`,id:n,className:`dsu-switch`,"aria-checked":t.checked,"aria-describedby":i,"data-field":t.field,disabled:t.disabled===!0,onClick:()=>{t.onChange(!t.checked)}},e(`span`,{className:`dsu-knob`})))}function v(t){let i=typeof t.value==`number`&&Number.isFinite(t.value)?String(t.value):``,[a,c]=s(i),[l,u]=s(!1),d=o(void 0),f=r(),p=r(),m=[t.hint===void 0?void 0:`${f}-hint`,l?p:void 0].filter(e=>e!==void 0),h=m.length===0?void 0:m.join(` `);n(()=>{t.value!==d.current&&(d.current=t.value,c(i),u(!1))},[t.value,i]);let _=e=>{let n=t.min??-1/0,r=t.max??1/0;if(!Number.isFinite(e)||e<n||e>r)return!1;let{step:i}=t;if(i===void 0||i<=0)return!0;let a=(e-(t.min??0))/i;return Math.abs(a-Math.round(a))<1e-9},v=e=>{if(c(e),e===``){u(!1),d.current=void 0,t.onChange(void 0);return}let n=Number(e);_(n)?(u(!1),d.current=n,t.onChange(n)):u(!0)},y=()=>{a===``||l||c(String(Number(a)))};return e(g,{...t,controlId:f},e(`div`,{className:`dsu-numberWrap`},e(`div`,{className:`dsu-number`},e(`input`,{className:`dsu-input`,type:`number`,inputMode:`numeric`,id:f,value:a,min:t.min,max:t.max,step:t.step,placeholder:t.placeholder,"aria-invalid":l||void 0,"aria-describedby":h,"data-field":t.field,disabled:t.disabled===!0,onChange:e=>{v(e.target.value)},onBlur:y}),t.unit===void 0?null:e(`span`,{className:`dsu-unit`},t.unit)),l?e(`p`,{className:`dsu-error`,id:p},t.invalidHint):null))}function y(t){let[i,a]=s(t.value),[o,c]=s(!1),l=r(),u=t.hint===void 0?void 0:`${l}-hint`;n(()=>{o||a(t.value)},[t.value,o]);let d={id:l,className:`dsu-input${t.multiline===!0?` dsu-textarea`:``}${t.monospace===!0?` dsu-mono`:``}`,value:i,placeholder:t.placeholder,"aria-describedby":u,"data-field":t.field,disabled:t.disabled===!0,onFocus:()=>{c(!0)},onBlur:()=>{c(!1)},onChange:e=>{a(e.target.value),t.onChange(e.target.value)}};return e(g,{...t,controlId:l},t.multiline===!0?e(`textarea`,d):e(`input`,d))}function b(t){let i=t.separator??`comma`,a=t.value.join(i===`newline`?`
84
- `:`, `),[o,c]=s(a),[l,u]=s(!1),d=r(),f=t.hint===void 0?void 0:`${d}-hint`;n(()=>{l||c(a)},[a,l]);let p=e=>e.split(i===`newline`?`
85
- `:`,`).map(e=>e.trim()).filter(e=>e.length>0),m={id:d,className:`dsu-input${i===`newline`?` dsu-textarea dsu-mono`:``}`,value:o,placeholder:t.placeholder,"aria-describedby":f,"data-field":t.field,disabled:t.disabled===!0,onFocus:()=>{u(!0)},onBlur:()=>{u(!1)},onChange:e=>{c(e.target.value),t.onChange(p(e.target.value))}};return e(g,{...t,controlId:d},e(i===`newline`?`textarea`:`input`,m))}function x(c){let[l,u]=s(!1),[d,f]=s(0),p=o(null),m=o(null),h=r(),_=r(),v=c.hint===void 0?void 0:`${_}-hint`,{disabled:y,value:b,options:x,onChange:S}=c,C=a(()=>x.map((e,t)=>({option:e,index:t})).filter(e=>e.option.disabled!==!0),[x]),w=x.find(e=>e.value===b)?.label??b,T=x[d],E=T!==void 0&&T.disabled!==!0,D=t(()=>{let e=p.current,t=m.current;
86
- /* v8 ignore next 3 -- place 只在 open 的 layout effect 里调用;refs 为空只可能是卸载竞态 */
87
- if(e===null||t===null)return;let n=e.getBoundingClientRect(),r=Math.min(n.width,window.innerWidth-16),{left:i}=n;i+r>window.innerWidth-8&&(i=window.innerWidth-r-8);let a=t.offsetHeight,o=n.bottom+6;o+a>window.innerHeight-8&&(o=n.top-a-6);let s=Math.max(8,window.innerWidth-r-8),c=Math.max(8,window.innerHeight-a-8);t.style.width=`${r}px`,t.style.left=`${Math.min(Math.max(8,i),s)}px`,t.style.top=`${Math.min(Math.max(8,o),c)}px`},[]),O=t(()=>{u(!1)},[]),k=t(()=>{if(y===!0)return;let e=C.find(e=>e.option.value===b)??C.at(0);e!==void 0&&(f(e.index),u(!0))},[y,b,C]),A=t(e=>{
88
- /* v8 ignore next 3 -- 浮层只能经由 openMenu 打开;组件禁用时打不开 */
89
- if(y===!0)return;let t=x[e];
90
- /* v8 ignore next 3 -- disabled 选项在浏览器里不派发 click;越界下标由调用面保证 */
91
- t!==void 0&&t.disabled!==!0&&(S(t.value),O(),p.current?.focus())},[y,x,S,O]),j=t(e=>{let t=C.findIndex(e=>e.index===d);
92
- /* v8 ignore next 5 -- open 时归一 effect 已保证 enabled 非空且 active 落在可用项上 */
93
- if(t===-1||C.length===0)return;let n=(t+e+C.length)%C.length,r=C[n];
94
- /* v8 ignore next 3 -- next 由取模保证在界内 */
95
- r!==void 0&&f(r.index)},[d,C]);i(()=>{if(!l)return()=>{};D();let e=e=>{let t=e.target;m.current?.contains(t)!==!0&&p.current?.contains(t)!==!0&&O()},t=e=>{let{target:t}=e;t instanceof Node&&m.current?.contains(t)===!0||O()},n=()=>{D()};return document.addEventListener(`pointerdown`,e,!0),window.addEventListener(`scroll`,t,!0),window.addEventListener(`resize`,n),()=>{document.removeEventListener(`pointerdown`,e,!0),window.removeEventListener(`scroll`,t,!0),window.removeEventListener(`resize`,n)}},[l,c.options,D,O]),n(()=>{if(!l)return;if(y===!0||C.length===0){O();return}if(C.some(e=>e.index===d))return;let e=C.find(e=>e.option.value===b)??C.at(0);
96
- /* v8 ignore next 3 -- enabled 非空在上一分支已保证 */
97
- e!==void 0&&f(e.index)},[l,y,b,x,d,O,C]),i(()=>{if(!l)return;let e=m.current;
98
- /* v8 ignore next 2 -- 同上,menu 在 open 的 commit 之后必然已挂上 */
99
- if(e===null)return;let t=e.querySelector(`[data-active="true"]`);
100
- /* v8 ignore next 2 -- active 已由归一 effect 保证落在可用项上 */
101
- t!==null&&(t.offsetTop<e.scrollTop?e.scrollTop=t.offsetTop:t.offsetTop+t.offsetHeight>e.scrollTop+e.clientHeight&&(e.scrollTop=t.offsetTop+t.offsetHeight-e.clientHeight))},[l,d,x]);let M=e=>{if(e.key===`ArrowDown`||e.key===`ArrowUp`){if(e.preventDefault(),!l){k();return}j(e.key===`ArrowDown`?1:-1)}else if(e.key===`Home`||e.key===`End`){if(e.preventDefault(),l||k(),C.length===0)return;let t=e.key===`Home`?C.at(0):C.at(-1);
102
- /* v8 ignore next 3 -- enabled 为空已在上一条返回 */
103
- if(t===void 0)return;f(t.index)}else if(e.key===`Enter`||e.key===` `){if(e.preventDefault(),!l){k();return}A(d)}else(e.key===`Tab`||e.key===`Escape`)&&O()},N=[],P;for(let[t,n]of x.entries())n.group!==P&&(n.group!==void 0&&N.push(e(`div`,{key:`group-${t}-${n.group}`,className:`dsu-optionGroup`,role:`presentation`},n.group)),P=n.group),N.push(e(`button`,{key:`option-${n.value}`,type:`button`,role:`option`,id:`${h}-${t}`,className:`dsu-option`,tabIndex:-1,"aria-selected":n.value===c.value,"data-active":t===d,disabled:n.disabled===!0,onMouseDown:e=>{e.preventDefault()},onClick:()=>{A(t)},onMouseEnter:()=>{
104
- /* v8 ignore next -- happy-dom 对 disabled 元素不派发 mouse 事件;浏览器实现各有差异,保留禁用检查 */
105
- n.disabled!==!0&&f(t)}},e(`span`,null,n.label),e(`svg`,{className:`dsu-check`,width:14,height:14,viewBox:`0 0 14 14`,"aria-hidden":!0},e(`path`,{d:`M2.5 7.5l3 3 6-7`,fill:`none`,stroke:`currentColor`,strokeWidth:1.5,strokeLinecap:`round`,strokeLinejoin:`round`}))));return e(g,{...c,controlId:_},e(`div`,{className:`dsu-selectWrap`,onBlur:e=>{let t=e.relatedTarget;t instanceof Node&&e.currentTarget.contains(t)||O()}},e(`button`,{ref:p,type:`button`,id:_,className:`dsu-select dsu-input`,role:`combobox`,"aria-haspopup":`listbox`,"aria-expanded":l,"aria-controls":`${h}-list`,"aria-activedescendant":l&&E?`${h}-${d}`:void 0,"aria-describedby":v,"data-field":c.field,disabled:y===!0||x.length===0,onClick:()=>{l?O():k()},onKeyDown:M},e(`span`,{className:`dsu-selectValue`},w),e(`svg`,{className:`dsu-chevron`,width:14,height:14,viewBox:`0 0 14 14`,"aria-hidden":!0},e(`path`,{d:`M3 5l4 4 4-4`,fill:`none`,stroke:`currentColor`,strokeWidth:1.5,strokeLinecap:`round`,strokeLinejoin:`round`}))),e(`div`,{ref:m,id:`${h}-list`,role:`listbox`,className:`dsu-menu`,"aria-label":c.label,hidden:!l},...N)))}function S(t){let n=!t.writable||t.busy||!t.dirty,r;return r=t.error===null?t.writable?t.dirty?t.dirtyLabel:t.cleanLabel:t.readOnlyLabel:t.error,e(`div`,{className:`dsu-savebar`,"data-dirty":t.dirty},e(`button`,{type:`button`,className:`dsu-btn dsu-btnPrimary`,"data-field":`save`,disabled:n,onClick:t.onSave},t.busy?t.savingLabel:t.saveLabel),e(`button`,{type:`button`,className:`dsu-btn`,"data-field":`discard`,disabled:n,onClick:t.onDiscard},t.revertLabel),e(`span`,{className:t.error===null?`dsu-status`:`dsu-status dsu-statusError`},r))}export{c as CARD_UI_CSS,u as CardForm,g as Field,f as FieldGrid,h as FieldLabel,m as InfoTip,b as ListField,v as NumberField,S as SaveBar,d as Section,x as SelectField,p as StatusBar,y as TextField,_ as ToggleField,l as splitHint};
1
+ export{h as CARD_UI_CSS,u as CardForm,s as Field,d as FieldGrid,c as FieldLabel,l as InfoTip,r as ListField,i as NumberField,t as SaveBar,f as Section,n as SelectField,p as StatusBar,a as TextField,o as ToggleField,m as splitHint}from"./card-ui-C1lrHILm.js";
@@ -0,0 +1 @@
1
+ import{a as e,c as t,d as n,f as r,h as i,i as a,l as o,m as s,n as c,o as l,p as u,r as d,s as f,t as p,u as m}from"./card-ui-C1lrHILm.js";import{diffTouched as h}from"./card-diff.js";import{claimApply as g}from"./card-apply.js";import{fieldOf as _,isRecord as v}from"./record.js";import{errorText as y}from"./errors.js";const b=`@jayyuen66/dsh-plugin-shared`;function x(){}export{i as CARD_UI_CSS,m as CardForm,f as Field,n as FieldGrid,t as FieldLabel,o as InfoTip,d as ListField,a as NumberField,p as SaveBar,r as Section,c as SelectField,u as StatusBar,e as TextField,l as ToggleField,x as apply,g as claimApply,h as diffTouched,y as errorText,_ as fieldOf,v as isRecord,b as name,s as splitHint};