@pixel-point/toolcraft 0.0.17 → 0.0.18
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/package.json +1 -1
- package/src/generate-test-doc-assertions.mjs +58 -68
- package/src/generate-test-doc-assertions.test.mjs +43 -0
- package/src/generate-test-doc-policy-assertions.mjs +92 -0
- package/src/generate-test-file-assertions.mjs +26 -75
- package/src/generate-test-product-iteration.test.mjs +17 -0
- package/src/generate-test-runtime-boundary-assertions.mjs +158 -0
- package/src/generate-test-standalone-assertions.mjs +10 -0
- package/templates/runtime/contracts/component-contracts.choices.test.ts +47 -3
- package/templates/runtime/contracts/component-contracts.choices.ts +48 -3
- package/templates/runtime/contracts/component-contracts.inputs.test.ts +6 -6
- package/templates/runtime/contracts/component-contracts.inputs.ts +6 -6
- package/templates/runtime/contracts/component-contracts.media-custom.test.ts +8 -2
- package/templates/runtime/contracts/component-contracts.media-custom.ts +8 -2
- package/templates/runtime/contracts/component-contracts.runtime-acceptance.test.ts +4 -4
- package/templates/runtime/contracts/component-contracts.runtime-labels.test.ts +7 -1
- package/templates/runtime/contracts/component-contracts.runtime-panels.test.ts +3 -0
- package/templates/runtime/contracts/component-contracts.runtime-shell.test.ts +3 -0
- package/templates/runtime/contracts/component-contracts.runtime.ts +12 -8
- package/templates/runtime/contracts/component-contracts.test.ts +2 -0
- package/templates/runtime/contracts/decision-contracts.test.ts +18 -0
- package/templates/runtime/contracts/decision-contracts.ts +10 -10
- package/templates/runtime/export/export-background.ts +0 -19
- package/templates/runtime/export/export.test.ts +45 -42
- package/templates/runtime/export/index.ts +44 -16
- package/templates/runtime/index.ts +2 -0
- package/templates/runtime/model-import/model-import-limits.ts +15 -4
- package/templates/runtime/react/app-shell/runtime-public-api.test.tsx +11 -1
- package/templates/runtime/react/app-shell/toolcraft-app.scene-export.test.tsx +29 -0
- package/templates/runtime/react/app-shell/toolcraft-app.tsx +18 -8
- package/templates/runtime/react/canvas/canvas-shell-rendering.test.tsx +76 -0
- package/templates/runtime/react/canvas/canvas-shell-test-utils.tsx +13 -1
- package/templates/runtime/react/canvas/canvas-shell.tsx +16 -1
- package/templates/runtime/react/canvas/product-scene-surface.test.tsx +195 -0
- package/templates/runtime/react/canvas/product-scene-surface.tsx +155 -0
- package/templates/runtime/react/canvas/runtime-scene-export.ts +1 -1
- package/templates/runtime/react/controls-panel/__tests__/control-conditions.test.ts +61 -0
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.collection-actions.test.tsx +89 -0
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.control-visibility.test.tsx +40 -19
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.scene-export-actions.test.tsx +1 -1
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-collection.test.tsx +62 -0
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-color-bank.test.tsx +67 -0
- package/templates/runtime/react/controls-panel/actions/controls-panel-actions.ts +2 -4
- package/templates/runtime/react/controls-panel/actions/export-action-runner.ts +10 -4
- package/templates/runtime/react/controls-panel/conditions/control-conditions.ts +32 -119
- package/templates/runtime/react/controls-panel/layout/controls-panel-control-group.tsx +5 -2
- package/templates/runtime/react/controls-panel/layout/controls-panel-layout.tsx +10 -8
- package/templates/runtime/react/controls-panel/layout/controls-panel-section.tsx +3 -3
- package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.test.tsx +63 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.tsx +138 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-items.tsx +104 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-renderer.tsx +115 -236
- package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.test.tsx +95 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.tsx +82 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.test.tsx +73 -1
- package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.tsx +50 -1
- package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.test.ts +1 -1
- package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.ts +2 -1
- package/templates/runtime/react/controls-panel/values/controls-panel-collection-values.ts +38 -6
- package/templates/runtime/react/controls-panel/values/controls-panel-value-labels.ts +1 -0
- package/templates/runtime/react/index.ts +4 -0
- package/templates/runtime/react/model-rendering/model-export.ts +1 -1
- package/templates/runtime/react/orientation-gizmo/orientation-gizmo-selection.ts +5 -5
- package/templates/runtime/react/orientation-gizmo/use-toolcraft-orientation-control-selection.ts +2 -2
- package/templates/runtime/scene/scene-bounds.test.ts +23 -0
- package/templates/runtime/scene/scene-bounds.ts +31 -17
- package/templates/runtime/schema/app-capabilities.test.ts +4 -0
- package/templates/runtime/schema/collection-item-controls.ts +29 -0
- package/templates/runtime/schema/control-applicability.test.ts +127 -0
- package/templates/runtime/schema/control-applicability.ts +433 -0
- package/templates/runtime/schema/control-schema-normalization.ts +177 -11
- package/templates/runtime/schema/controls-panel-actions.ts +2 -2
- package/templates/runtime/schema/controls-panel-section-layout.ts +14 -9
- package/templates/runtime/schema/controls-panel-section-roundtrip.test.ts +59 -0
- package/templates/runtime/schema/define-toolcraft.collection-actions-item-controls.test.ts +127 -0
- package/templates/runtime/schema/define-toolcraft.control-sections.test.ts +10 -4
- package/templates/runtime/schema/define-toolcraft.file-drop-item-controls.test.ts +98 -0
- package/templates/runtime/schema/define-toolcraft.file-drop-normalization.test.ts +27 -0
- package/templates/runtime/schema/define-toolcraft.segmented-control-fit.test.ts +20 -2
- package/templates/runtime/schema/define-toolcraft.setup-canvas.test.ts +16 -3
- package/templates/runtime/schema/define-toolcraft.source-collection.test.ts +79 -0
- package/templates/runtime/schema/runtime-setup-section.ts +24 -5
- package/templates/runtime/schema/types.ts +68 -13
- package/templates/starter/AGENTS.md +12 -10
- package/templates/starter/docs/toolcraft/acceptance-testing.md +17 -31
- package/templates/starter/docs/toolcraft/agent-worklog.md +15 -0
- package/templates/starter/docs/toolcraft/assembly-workflow.md +10 -3
- package/templates/starter/docs/toolcraft/component-rules.md +8 -6
- package/templates/starter/docs/toolcraft/core/control-selection.md +9 -3
- package/templates/starter/docs/toolcraft/core/layout.md +7 -7
- package/templates/starter/docs/toolcraft/core/media-upload.md +2 -0
- package/templates/starter/docs/toolcraft/core/runtime-boundary.md +13 -2
- package/templates/starter/docs/toolcraft/core/setup-export.md +33 -7
- package/templates/starter/docs/toolcraft/custom-controls.md +6 -4
- package/templates/starter/docs/toolcraft/decision-contract.md +3 -1
- package/templates/starter/docs/toolcraft/renderer-technique.md +4 -0
- package/templates/starter/docs/toolcraft/schema-reference.md +21 -19
- package/templates/starter/e2e/app-browser-control-applicability-requirements.spec.ts +163 -0
- package/templates/starter/e2e/app-browser-infinity-canvas-evidence.spec.ts +7 -2
- package/templates/starter/e2e/app-browser-orientation-evidence.spec.ts +174 -21
- package/templates/starter/e2e/app-browser-runtime-requirements.spec.ts +1 -33
- package/templates/starter/e2e/app-browser-semantic-evidence.spec.ts +61 -15
- package/templates/starter/e2e/{browser-conditional-output-evidence-helpers.ts → browser-background-output-evidence.ts} +0 -68
- package/templates/starter/e2e/browser-control-applicability-evidence.ts +114 -0
- package/templates/starter/e2e/browser-infinity-canvas-evidence.ts +20 -11
- package/templates/starter/e2e/browser-orientation-gizmo-evidence-helpers.ts +4 -0
- package/templates/starter/e2e/browser-orientation-gizmo-live-preconditions.ts +86 -0
- package/templates/starter/e2e/browser-runtime-applicability-requirements.ts +84 -0
- package/templates/starter/e2e/browser-runtime-evidence-reporter.ts +9 -2
- package/templates/starter/e2e/browser-runtime-evidence-requirements.ts +29 -26
- package/templates/starter/e2e/browser-semantic-evidence-test-helpers.ts +25 -0
- package/templates/starter/scripts/toolcraft-contract-manifest.json +1 -0
- package/templates/starter/scripts/toolcraft-product-boundary-ast.mjs +85 -115
- package/templates/starter/scripts/toolcraft-product-boundary-module-policy.mjs +101 -0
- package/templates/starter/scripts/toolcraft-product-boundary.test.mjs +0 -72
- package/templates/starter/scripts/toolcraft-product-control-bindings.mjs +182 -0
- package/templates/starter/scripts/toolcraft-product-control-boundary.mjs +146 -0
- package/templates/starter/scripts/toolcraft-product-control-boundary.test.mjs +186 -0
- package/templates/starter/scripts/toolcraft-product-export-boundary.mjs +6 -17
- package/templates/starter/scripts/toolcraft-product-export-boundary.test.mjs +196 -0
- package/templates/starter/scripts/toolcraft-typescript-source-evidence-structure.test.mjs +20 -0
- package/templates/starter/scripts/toolcraft-typescript-source-evidence.mjs +1 -0
- package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade.test.mjs +2 -0
- package/templates/starter/src/app/acceptance/artifact-export-intent.ts +130 -0
- package/templates/starter/src/app/acceptance/conditions.ts +1 -1
- package/templates/starter/src/app/acceptance/control-acceptance-context.ts +2 -2
- package/templates/starter/src/app/acceptance/control-acceptance-coverage.ts +6 -20
- package/templates/starter/src/app/acceptance/control-acceptance-kind-rules.ts +24 -5
- package/templates/starter/src/app/acceptance/control-acceptance-policy.ts +2 -13
- package/templates/starter/src/app/acceptance/control-applicability-cases.test.ts +321 -0
- package/templates/starter/src/app/acceptance/control-applicability-cases.ts +290 -0
- package/templates/starter/src/app/acceptance/control-applicability.ts +332 -0
- package/templates/starter/src/app/acceptance/control-layout-dependency-rules.ts +20 -9
- package/templates/starter/src/app/acceptance/control-layout-entity-rules.ts +4 -6
- package/templates/starter/src/app/acceptance/control-layout-model.ts +4 -4
- package/templates/starter/src/app/acceptance/control-layout-section-rules.ts +3 -10
- package/templates/starter/src/app/acceptance/control-parts.ts +8 -0
- package/templates/starter/src/app/acceptance/control-section-entity-cohesion.test.ts +215 -0
- package/templates/starter/src/app/acceptance/control-section-entity-cohesion.ts +132 -0
- package/templates/starter/src/app/acceptance/control-section-inventory.test.ts +87 -0
- package/templates/starter/src/app/acceptance/control-section-inventory.ts +74 -44
- package/templates/starter/src/app/acceptance/custom-controls.ts +11 -3
- package/templates/starter/src/app/acceptance/inline-layout.ts +64 -9
- package/templates/starter/src/app/acceptance/media-upload.ts +5 -1
- package/templates/starter/src/app/acceptance/orientation-gizmo.ts +18 -11
- package/templates/starter/src/app/acceptance/output-background-rules.ts +2 -2
- package/templates/starter/src/app/acceptance/output-export-layout-rules.ts +18 -10
- package/templates/starter/src/app/acceptance/output-export-model.ts +54 -11
- package/templates/starter/src/app/acceptance/output-export.ts +22 -10
- package/templates/starter/src/app/acceptance/output-video-export-rules.ts +99 -0
- package/templates/starter/src/app/acceptance/types.ts +23 -8
- package/templates/starter/src/app/acceptance/validate-coverage.ts +19 -2
- package/templates/starter/src/app/starter-acceptance.artifact-export-intent.test.ts +239 -0
- package/templates/starter/src/app/starter-acceptance.background-export.test.ts +33 -0
- package/templates/starter/src/app/starter-acceptance.canvas-sizing.test.ts +16 -1
- package/templates/starter/src/app/starter-acceptance.compound-part-coverage.test.ts +65 -0
- package/templates/starter/src/app/starter-acceptance.contract-fixtures.test.ts +41 -0
- package/templates/starter/src/app/starter-acceptance.contract-fixtures.ts +112 -16
- package/templates/starter/src/app/starter-acceptance.control-applicability.test.ts +224 -0
- package/templates/starter/src/app/starter-acceptance.control-order.test.ts +58 -3
- package/templates/starter/src/app/starter-acceptance.control-state.test.ts +0 -111
- package/templates/starter/src/app/starter-acceptance.custom-control-collections.test.ts +3 -1
- package/templates/starter/src/app/starter-acceptance.custom-control-valid.test.ts +1 -1
- package/templates/starter/src/app/starter-acceptance.export-test-utils.ts +22 -0
- package/templates/starter/src/app/{starter-acceptance.image-video-export-settings.test.ts → starter-acceptance.image-export-settings.test.ts} +37 -9
- package/templates/starter/src/app/starter-acceptance.interaction-ownership.test.ts +12 -0
- package/templates/starter/src/app/starter-acceptance.media-upload.fixtures.ts +11 -1
- package/templates/starter/src/app/starter-acceptance.media-upload.test.ts +28 -0
- package/templates/starter/src/app/starter-acceptance.output-export-intent.test.ts +384 -0
- package/templates/starter/src/app/starter-acceptance.output-export-rules.test.ts +4 -0
- package/templates/starter/src/app/starter-acceptance.product-output-export.test.ts +112 -105
- package/templates/starter/src/app/starter-acceptance.product-readiness.test.ts +12 -0
- package/templates/starter/src/app/starter-acceptance.section-cohesion.test.ts +1 -1
- package/templates/starter/src/app/starter-acceptance.section-dependencies.test.ts +35 -24
- package/templates/starter/src/app/starter-acceptance.section-size.test.ts +72 -0
- package/templates/starter/src/app/starter-acceptance.ts +11 -2
- package/templates/starter/src/app/starter-acceptance.video-export-settings.test.ts +336 -0
- package/templates/starter/src/app/starter-acceptance.view-interaction.test.ts +8 -0
- package/templates/starter/src/app/test-evidence/browser-runtime-contract.ts +2 -2
- package/templates/starter/src/routes/index.tsx +1 -0
- package/templates/ui/components/composites/tabs.tsx +2 -2
- package/templates/ui/components/controls/collection-actions/collection-item-groups.test.tsx +38 -0
- package/templates/ui/components/controls/collection-actions/collection-item-groups.tsx +36 -0
- package/templates/ui/components/controls/collection-actions/index.ts +2 -0
- package/templates/ui/components/controls/file-drop/file-drop-collection-control.tsx +137 -0
- package/templates/ui/components/controls/file-drop/file-drop-control.test.tsx +189 -0
- package/templates/ui/components/controls/file-drop/file-drop-control.tsx +81 -88
- package/templates/ui/components/controls/file-drop/file-drop-presentation.tsx +131 -9
- package/templates/ui/components/controls/file-drop/file-drop-types.ts +36 -22
- package/templates/ui/components/controls/file-drop/index.ts +1 -0
- package/templates/ui/components/controls/index.ts +3 -0
- package/templates/ui/components/controls/range-slider/range-slider-control.tsx +1 -1
- package/templates/ui/components/controls/range-slider/range-slider-value.test.ts +14 -0
- package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -2
- package/templates/starter/src/app/acceptance/condition-exclusivity.test.ts +0 -60
- package/templates/starter/src/app/acceptance/condition-exclusivity.ts +0 -221
|
@@ -94,3 +94,192 @@ describe("FileDropControl folder selection", () => {
|
|
|
94
94
|
expect(screen.queryByLabelText("Upload file folder")).toBeNull();
|
|
95
95
|
});
|
|
96
96
|
});
|
|
97
|
+
|
|
98
|
+
describe("FileDropControl collection actions", () => {
|
|
99
|
+
const filePresentation = {
|
|
100
|
+
accept: "image/*",
|
|
101
|
+
allowsFileBatch: true,
|
|
102
|
+
allowsFolderSelection: false,
|
|
103
|
+
emptyDescription: "or drop bump maps",
|
|
104
|
+
emptyTitle: "Upload bump maps",
|
|
105
|
+
items: [],
|
|
106
|
+
presenter: "file-list",
|
|
107
|
+
secondaryActions: [],
|
|
108
|
+
} satisfies FileDropPresentation<"file">;
|
|
109
|
+
|
|
110
|
+
it("adds an empty upload slot before selecting another file", () => {
|
|
111
|
+
const onFilesSelect = vi.fn();
|
|
112
|
+
|
|
113
|
+
render(
|
|
114
|
+
<FileDropControl
|
|
115
|
+
collectionActions={{
|
|
116
|
+
addLabel: "Add bump map",
|
|
117
|
+
itemLabel: "bump map",
|
|
118
|
+
name: "Bump maps",
|
|
119
|
+
removeLabel: "Remove bump map",
|
|
120
|
+
}}
|
|
121
|
+
onFilesSelect={onFilesSelect}
|
|
122
|
+
presentation={{
|
|
123
|
+
...filePresentation,
|
|
124
|
+
items: [{
|
|
125
|
+
assetKind: "file",
|
|
126
|
+
fileName: "broad.png",
|
|
127
|
+
id: "bump-broad",
|
|
128
|
+
}],
|
|
129
|
+
}}
|
|
130
|
+
/>,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
fireEvent.click(screen.getByRole("button", { name: "Add bump map" }));
|
|
134
|
+
expect(onFilesSelect).not.toHaveBeenCalled();
|
|
135
|
+
expect(
|
|
136
|
+
(screen.getByRole("button", { name: "Add bump map" }) as HTMLButtonElement)
|
|
137
|
+
.disabled,
|
|
138
|
+
).toBe(true);
|
|
139
|
+
|
|
140
|
+
const input = screen.getByLabelText("Upload bump map 2") as HTMLInputElement;
|
|
141
|
+
const file = new File([new Uint8Array([1])], "grain.png", {
|
|
142
|
+
type: "image/png",
|
|
143
|
+
});
|
|
144
|
+
fireEvent.change(input, { target: { files: [file] } });
|
|
145
|
+
|
|
146
|
+
expect(onFilesSelect).toHaveBeenCalledWith([file]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("removes the final item and suppresses duplicate list cardinality actions", () => {
|
|
150
|
+
const onItemRemove = vi.fn();
|
|
151
|
+
const finalItem = {
|
|
152
|
+
assetKind: "file" as const,
|
|
153
|
+
fileName: "fine.png",
|
|
154
|
+
id: "bump-fine",
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
render(
|
|
158
|
+
<FileDropControl
|
|
159
|
+
collectionActions={{
|
|
160
|
+
addLabel: "Add bump map",
|
|
161
|
+
itemLabel: "bump map",
|
|
162
|
+
name: "Bump maps",
|
|
163
|
+
removeLabel: "Remove bump map",
|
|
164
|
+
}}
|
|
165
|
+
onFilesSelect={vi.fn()}
|
|
166
|
+
onItemRemove={onItemRemove}
|
|
167
|
+
presentation={{
|
|
168
|
+
...filePresentation,
|
|
169
|
+
items: [
|
|
170
|
+
{
|
|
171
|
+
assetKind: "file",
|
|
172
|
+
fileName: "broad.png",
|
|
173
|
+
id: "bump-broad",
|
|
174
|
+
},
|
|
175
|
+
finalItem,
|
|
176
|
+
],
|
|
177
|
+
}}
|
|
178
|
+
/>,
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
expect(screen.queryByRole("button", { name: "Add a new file" })).toBeNull();
|
|
182
|
+
expect(screen.queryByRole("button", { name: "Remove broad.png" })).toBeNull();
|
|
183
|
+
expect(screen.queryByRole("button", { name: "Remove fine.png" })).toBeNull();
|
|
184
|
+
|
|
185
|
+
fireEvent.click(screen.getByRole("button", { name: "Remove bump map" }));
|
|
186
|
+
expect(onItemRemove).toHaveBeenCalledWith(finalItem, 1);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("removes a pending final slot before deleting an attached file", () => {
|
|
190
|
+
const onItemRemove = vi.fn();
|
|
191
|
+
|
|
192
|
+
render(
|
|
193
|
+
<FileDropControl
|
|
194
|
+
collectionActions={{
|
|
195
|
+
addLabel: "Add bump map",
|
|
196
|
+
itemLabel: "bump map",
|
|
197
|
+
name: "Bump maps",
|
|
198
|
+
removeLabel: "Remove bump map",
|
|
199
|
+
}}
|
|
200
|
+
onFilesSelect={vi.fn()}
|
|
201
|
+
onItemRemove={onItemRemove}
|
|
202
|
+
presentation={{
|
|
203
|
+
...filePresentation,
|
|
204
|
+
items: [{
|
|
205
|
+
assetKind: "file",
|
|
206
|
+
fileName: "broad.png",
|
|
207
|
+
id: "bump-broad",
|
|
208
|
+
}],
|
|
209
|
+
}}
|
|
210
|
+
/>,
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
fireEvent.click(screen.getByRole("button", { name: "Add bump map" }));
|
|
214
|
+
expect(screen.getByLabelText("Upload bump map 2")).toBeTruthy();
|
|
215
|
+
|
|
216
|
+
fireEvent.click(screen.getByRole("button", { name: "Remove bump map" }));
|
|
217
|
+
expect(screen.queryByLabelText("Upload bump map 2")).toBeNull();
|
|
218
|
+
expect(onItemRemove).not.toHaveBeenCalled();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("renders item-owned settings only under an attached upload", () => {
|
|
222
|
+
const renderItemContent = vi.fn((item: { id: string }) => (
|
|
223
|
+
<div>Settings for {item.id}</div>
|
|
224
|
+
));
|
|
225
|
+
|
|
226
|
+
render(
|
|
227
|
+
<FileDropControl
|
|
228
|
+
collectionActions={{
|
|
229
|
+
addLabel: "Add bump map",
|
|
230
|
+
itemLabel: "bump map",
|
|
231
|
+
name: "Bump maps",
|
|
232
|
+
renderItemContent,
|
|
233
|
+
}}
|
|
234
|
+
onFilesSelect={vi.fn()}
|
|
235
|
+
presentation={{
|
|
236
|
+
...filePresentation,
|
|
237
|
+
items: [
|
|
238
|
+
{
|
|
239
|
+
assetKind: "file",
|
|
240
|
+
fileName: "broad.png",
|
|
241
|
+
id: "bump-broad",
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
}}
|
|
245
|
+
/>,
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
expect(screen.getByText("Settings for bump-broad")).toBeTruthy();
|
|
249
|
+
fireEvent.click(screen.getByRole("button", { name: "Add bump map" }));
|
|
250
|
+
expect(screen.getByLabelText("Upload bump map 2")).toBeTruthy();
|
|
251
|
+
expect(renderItemContent).toHaveBeenCalled();
|
|
252
|
+
expect(
|
|
253
|
+
renderItemContent.mock.calls.every(([item]) => item.id === "bump-broad"),
|
|
254
|
+
).toBe(true);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("separates attached logical file groups with one between-group line", () => {
|
|
258
|
+
const { container } = render(
|
|
259
|
+
<FileDropControl
|
|
260
|
+
collectionActions={{
|
|
261
|
+
itemLabel: "bump map",
|
|
262
|
+
name: "Bump maps",
|
|
263
|
+
renderItemContent: (item) => <div>Settings for {item.id}</div>,
|
|
264
|
+
}}
|
|
265
|
+
onFilesSelect={vi.fn()}
|
|
266
|
+
onItemRemove={vi.fn()}
|
|
267
|
+
presentation={{
|
|
268
|
+
...filePresentation,
|
|
269
|
+
items: [
|
|
270
|
+
{ assetKind: "file", fileName: "broad.png", id: "broad" },
|
|
271
|
+
{ assetKind: "file", fileName: "fine.png", id: "fine" },
|
|
272
|
+
],
|
|
273
|
+
}}
|
|
274
|
+
/>,
|
|
275
|
+
);
|
|
276
|
+
const groups = container.querySelectorAll(
|
|
277
|
+
'[data-slot="collection-item-group"]',
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
expect(groups).toHaveLength(2);
|
|
281
|
+
expect(groups[0]?.className).not.toContain("border-t");
|
|
282
|
+
expect(groups[1]?.className).toContain("border-t");
|
|
283
|
+
expect(container.textContent).not.toMatch(/Item 1|Item 2/u);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
@@ -11,12 +11,9 @@ import {
|
|
|
11
11
|
import { sortableKeyboardCoordinates } from "@dnd-kit/sortable";
|
|
12
12
|
|
|
13
13
|
import { cn } from "../../../lib/utils";
|
|
14
|
-
import {
|
|
15
|
-
ActionsControl,
|
|
16
|
-
type ActionControlObjectOption,
|
|
17
|
-
type ActionsControlItemState,
|
|
18
|
-
} from "../actions/actions-control";
|
|
14
|
+
import { ActionsControl } from "../actions/actions-control";
|
|
19
15
|
import { Field } from "../../primitives";
|
|
16
|
+
import { FileDropCollectionControl } from "./file-drop-collection-control";
|
|
20
17
|
import { FileDropEmptyState } from "./file-drop-empty-state";
|
|
21
18
|
import { FileDropFileList } from "./file-drop-file-list";
|
|
22
19
|
import { FileDropFolderInput } from "./file-drop-folder-input";
|
|
@@ -24,11 +21,13 @@ import { FileDropImageGrid } from "./file-drop-image-grid";
|
|
|
24
21
|
import { FileDropPresentationMessages } from "./file-drop-presentation-messages";
|
|
25
22
|
import {
|
|
26
23
|
createLegacyFileDropPresentation,
|
|
24
|
+
createFileDropActionModel,
|
|
25
|
+
createFileDropItemView,
|
|
26
|
+
createFileDropTargetHandlers,
|
|
27
27
|
getFileInputAccept,
|
|
28
28
|
getPresentationItemKey,
|
|
29
29
|
getPreviewKey,
|
|
30
30
|
imageTransformActions,
|
|
31
|
-
isDragLeavingCurrentTarget,
|
|
32
31
|
reorderFileDropPresentationItems,
|
|
33
32
|
} from "./file-drop-presentation";
|
|
34
33
|
import { FileDropSinglePreview } from "./file-drop-single-preview";
|
|
@@ -37,19 +36,19 @@ import type {
|
|
|
37
36
|
FileDropControlProps,
|
|
38
37
|
FileDropImageTransformOperation,
|
|
39
38
|
FileDropPresentation,
|
|
40
|
-
FileDropPresentationAction,
|
|
41
|
-
FileDropPresentationEntry,
|
|
42
39
|
FileDropPresentationItem,
|
|
43
40
|
FileDropPreview,
|
|
44
41
|
} from "./file-drop-types";
|
|
45
42
|
|
|
46
|
-
|
|
43
|
+
function FileDropControlBase<
|
|
47
44
|
AssetKind extends string = FileDropAssetKind,
|
|
48
45
|
Feedback = unknown,
|
|
49
46
|
Status = unknown,
|
|
50
47
|
>({
|
|
51
48
|
accept,
|
|
52
49
|
assetKind = "image",
|
|
50
|
+
collectionActions,
|
|
51
|
+
collectionSlot = false,
|
|
53
52
|
multiple = false,
|
|
54
53
|
onAction,
|
|
55
54
|
onClear,
|
|
@@ -65,11 +64,15 @@ export function FileDropControl<
|
|
|
65
64
|
presentation: providedPresentation,
|
|
66
65
|
preview,
|
|
67
66
|
previews,
|
|
68
|
-
}: FileDropControlProps<AssetKind, Feedback, Status>
|
|
67
|
+
}: FileDropControlProps<AssetKind, Feedback, Status> & {
|
|
68
|
+
collectionSlot?: boolean;
|
|
69
|
+
}): React.JSX.Element {
|
|
69
70
|
const inputRef = React.useRef<HTMLInputElement>(null);
|
|
70
71
|
const folderInputRef = React.useRef<HTMLInputElement>(null);
|
|
71
72
|
const [dragOver, setDragOver] = React.useState(false);
|
|
72
|
-
const [selectedItemKey, setSelectedItemKey] = React.useState<string | null>(
|
|
73
|
+
const [selectedItemKey, setSelectedItemKey] = React.useState<string | null>(
|
|
74
|
+
null,
|
|
75
|
+
);
|
|
73
76
|
const sensors = useSensors(
|
|
74
77
|
useSensor(PointerSensor, {
|
|
75
78
|
activationConstraint: {
|
|
@@ -88,32 +91,21 @@ export function FileDropControl<
|
|
|
88
91
|
previews,
|
|
89
92
|
});
|
|
90
93
|
const usesLegacyPresentation = providedPresentation === undefined;
|
|
91
|
-
const presentation = (providedPresentation ??
|
|
92
|
-
string,
|
|
93
|
-
unknown,
|
|
94
|
-
unknown
|
|
95
|
-
>;
|
|
94
|
+
const presentation = (providedPresentation ??
|
|
95
|
+
legacy.presentation) as FileDropPresentation<string, unknown, unknown>;
|
|
96
96
|
const isImagePresenter =
|
|
97
97
|
presentation.presenter === "image-grid" ||
|
|
98
98
|
presentation.presenter === "single-image";
|
|
99
99
|
const requiresPreviewSource = usesLegacyPresentation || isImagePresenter;
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
},
|
|
110
|
-
],
|
|
111
|
-
);
|
|
112
|
-
const renderableItems = itemEntries.map((entry) => entry.item);
|
|
113
|
-
const hasContent = itemEntries.length > 0;
|
|
114
|
-
const isListPresenter =
|
|
115
|
-
presentation.presenter === "file-list" || presentation.presenter === "model-item";
|
|
116
|
-
const itemKeys = itemEntries.map((entry) => entry.key);
|
|
100
|
+
const {
|
|
101
|
+
entries: itemEntries,
|
|
102
|
+
hasContent,
|
|
103
|
+
isListPresenter,
|
|
104
|
+
itemKeys,
|
|
105
|
+
items: renderableItems,
|
|
106
|
+
} = createFileDropItemView(presentation, requiresPreviewSource);
|
|
107
|
+
const isAttachedCollectionSlot = collectionSlot && hasContent;
|
|
108
|
+
const showsListCardinalityActions = !collectionSlot;
|
|
117
109
|
const legacyPreviewByKey = new Map<string, FileDropPreview>(
|
|
118
110
|
legacy.previews.map((item, index) => [getPreviewKey(item, index), item]),
|
|
119
111
|
);
|
|
@@ -135,22 +127,12 @@ export function FileDropControl<
|
|
|
135
127
|
Boolean(onPreviewTransform) &&
|
|
136
128
|
Boolean(selectedItemEntry) &&
|
|
137
129
|
(!requiresItemSelection || selectedItemKey !== null);
|
|
138
|
-
const secondaryActions
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
(
|
|
144
|
-
);
|
|
145
|
-
const actionStates = secondaryActions.reduce<Record<string, ActionsControlItemState>>(
|
|
146
|
-
(states, action) => {
|
|
147
|
-
if (action.state) {
|
|
148
|
-
states[action.value] = action.state;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return states;
|
|
152
|
-
},
|
|
153
|
-
{},
|
|
130
|
+
const secondaryActions = shouldRenderLegacyImageActions
|
|
131
|
+
? imageTransformActions
|
|
132
|
+
: presentation.secondaryActions;
|
|
133
|
+
const actionModel = createFileDropActionModel(
|
|
134
|
+
secondaryActions,
|
|
135
|
+
Boolean(shouldRenderLegacyImageActions || onAction),
|
|
154
136
|
);
|
|
155
137
|
const hasReorderHandler = usesLegacyPresentation
|
|
156
138
|
? Boolean(onPreviewReorder)
|
|
@@ -158,6 +140,11 @@ export function FileDropControl<
|
|
|
158
140
|
const canRemoveCollectionItem = usesLegacyPresentation
|
|
159
141
|
? Boolean(onPreviewRemove)
|
|
160
142
|
: Boolean(onItemRemove || (onClear && renderableItems.length === 1));
|
|
143
|
+
const dropTargetHandlers = createFileDropTargetHandlers({
|
|
144
|
+
enabled: !isAttachedCollectionSlot,
|
|
145
|
+
onDragOverChange: setDragOver,
|
|
146
|
+
onFiles: handleFiles,
|
|
147
|
+
});
|
|
161
148
|
|
|
162
149
|
if (
|
|
163
150
|
!usesLegacyPresentation &&
|
|
@@ -233,12 +220,14 @@ export function FileDropControl<
|
|
|
233
220
|
}
|
|
234
221
|
|
|
235
222
|
if (usesLegacyPresentation) {
|
|
236
|
-
const orderedPreviews = orderedPresentationItems.flatMap(
|
|
237
|
-
|
|
238
|
-
|
|
223
|
+
const orderedPreviews = orderedPresentationItems.flatMap(
|
|
224
|
+
(item, index) => {
|
|
225
|
+
const key = getPresentationItemKey(item, index);
|
|
226
|
+
const legacyPreview = legacyPreviewByKey.get(key);
|
|
239
227
|
|
|
240
|
-
|
|
241
|
-
|
|
228
|
+
return legacyPreview ? [legacyPreview] : [];
|
|
229
|
+
},
|
|
230
|
+
);
|
|
242
231
|
|
|
243
232
|
if (orderedPreviews.length === legacy.previews.length) {
|
|
244
233
|
onPreviewReorder?.(orderedPreviews);
|
|
@@ -256,7 +245,9 @@ export function FileDropControl<
|
|
|
256
245
|
index: number,
|
|
257
246
|
): void {
|
|
258
247
|
if (usesLegacyPresentation) {
|
|
259
|
-
const legacyPreview = legacyPreviewByKey.get(
|
|
248
|
+
const legacyPreview = legacyPreviewByKey.get(
|
|
249
|
+
getPresentationItemKey(item, index),
|
|
250
|
+
);
|
|
260
251
|
|
|
261
252
|
if (legacyPreview) {
|
|
262
253
|
onPreviewRemove?.(legacyPreview, index);
|
|
@@ -293,7 +284,9 @@ export function FileDropControl<
|
|
|
293
284
|
onClear?.();
|
|
294
285
|
}
|
|
295
286
|
|
|
296
|
-
function runLegacyImageTransform(
|
|
287
|
+
function runLegacyImageTransform(
|
|
288
|
+
operation: FileDropImageTransformOperation,
|
|
289
|
+
): void {
|
|
297
290
|
if (!selectedItemEntry) {
|
|
298
291
|
return;
|
|
299
292
|
}
|
|
@@ -314,6 +307,20 @@ export function FileDropControl<
|
|
|
314
307
|
onAction?.(value);
|
|
315
308
|
}
|
|
316
309
|
|
|
310
|
+
if (collectionActions) {
|
|
311
|
+
return (
|
|
312
|
+
<FileDropCollectionControl
|
|
313
|
+
actions={collectionActions}
|
|
314
|
+
canRemoveAttachedItem={canRemoveCollectionItem}
|
|
315
|
+
entries={itemEntries}
|
|
316
|
+
onFilesSelect={onFilesSelect}
|
|
317
|
+
onRemoveAttachedItem={handleCollectionItemRemove}
|
|
318
|
+
presentation={presentation}
|
|
319
|
+
SlotControl={FileDropControlBase}
|
|
320
|
+
/>
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
317
324
|
return (
|
|
318
325
|
<Field className="min-w-0" style={{ gap: "6px" }}>
|
|
319
326
|
<input
|
|
@@ -353,25 +360,8 @@ export function FileDropControl<
|
|
|
353
360
|
: "p-0",
|
|
354
361
|
)}
|
|
355
362
|
data-drag-over={dragOver}
|
|
356
|
-
onDragEnter={(event) => {
|
|
357
|
-
event.preventDefault();
|
|
358
|
-
setDragOver(true);
|
|
359
|
-
}}
|
|
360
|
-
onDragLeave={(event) => {
|
|
361
|
-
if (isDragLeavingCurrentTarget(event)) {
|
|
362
|
-
setDragOver(false);
|
|
363
|
-
}
|
|
364
|
-
}}
|
|
365
|
-
onDragOver={(event) => {
|
|
366
|
-
event.preventDefault();
|
|
367
|
-
setDragOver(true);
|
|
368
|
-
}}
|
|
369
|
-
onDrop={(event) => {
|
|
370
|
-
event.preventDefault();
|
|
371
|
-
setDragOver(false);
|
|
372
|
-
handleFiles(event.dataTransfer?.files ?? undefined);
|
|
373
|
-
}}
|
|
374
363
|
role="group"
|
|
364
|
+
{...dropTargetHandlers}
|
|
375
365
|
>
|
|
376
366
|
{!hasContent ? (
|
|
377
367
|
<FileDropEmptyState
|
|
@@ -385,10 +375,12 @@ export function FileDropControl<
|
|
|
385
375
|
) : presentation.presenter === "file-list" ? (
|
|
386
376
|
<FileDropFileList
|
|
387
377
|
isSortable={hasReorderHandler && itemEntries.length > 1}
|
|
388
|
-
onAddFile={openFileDialog}
|
|
378
|
+
onAddFile={showsListCardinalityActions ? openFileDialog : undefined}
|
|
389
379
|
onDragEnd={handleItemDragEnd}
|
|
390
380
|
onItemRemove={
|
|
391
|
-
|
|
381
|
+
showsListCardinalityActions && canRemoveCollectionItem
|
|
382
|
+
? handleCollectionItemRemove
|
|
383
|
+
: undefined
|
|
392
384
|
}
|
|
393
385
|
previewEntries={itemEntries}
|
|
394
386
|
previewKeys={itemKeys}
|
|
@@ -405,7 +397,9 @@ export function FileDropControl<
|
|
|
405
397
|
onPreviewSelect={
|
|
406
398
|
canSelectImageItem
|
|
407
399
|
? (key) => {
|
|
408
|
-
const entry = itemEntries.find(
|
|
400
|
+
const entry = itemEntries.find(
|
|
401
|
+
(candidate) => candidate.key === key,
|
|
402
|
+
);
|
|
409
403
|
|
|
410
404
|
if (!entry) {
|
|
411
405
|
return;
|
|
@@ -465,17 +459,8 @@ export function FileDropControl<
|
|
|
465
459
|
/>
|
|
466
460
|
{secondaryActions.length > 0 ? (
|
|
467
461
|
<ActionsControl
|
|
468
|
-
actionStates={
|
|
469
|
-
|
|
470
|
-
? actionStates
|
|
471
|
-
: Object.fromEntries(
|
|
472
|
-
secondaryActions.map((action) => [
|
|
473
|
-
action.value,
|
|
474
|
-
{ ...action.state, disabled: true },
|
|
475
|
-
]),
|
|
476
|
-
)
|
|
477
|
-
}
|
|
478
|
-
actions={actionOptions}
|
|
462
|
+
actionStates={actionModel.states}
|
|
463
|
+
actions={actionModel.options}
|
|
479
464
|
buttonColumns={isImagePresenter ? 3 : undefined}
|
|
480
465
|
name={isImagePresenter ? "Image transforms" : "File actions"}
|
|
481
466
|
onAction={handleSecondaryAction}
|
|
@@ -485,3 +470,11 @@ export function FileDropControl<
|
|
|
485
470
|
</Field>
|
|
486
471
|
);
|
|
487
472
|
}
|
|
473
|
+
|
|
474
|
+
export function FileDropControl<
|
|
475
|
+
AssetKind extends string = FileDropAssetKind,
|
|
476
|
+
Feedback = unknown,
|
|
477
|
+
Status = unknown,
|
|
478
|
+
>(props: FileDropControlProps<AssetKind, Feedback, Status>): React.JSX.Element {
|
|
479
|
+
return <FileDropControlBase {...props} />;
|
|
480
|
+
}
|
|
@@ -3,13 +3,18 @@
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
|
|
5
5
|
import { cn } from "../../../lib/utils";
|
|
6
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
ActionControlObjectOption,
|
|
8
|
+
ActionControlOption,
|
|
9
|
+
ActionsControlItemState,
|
|
10
|
+
} from "../actions/actions-control";
|
|
7
11
|
import type {
|
|
8
12
|
FileDropAssetKind,
|
|
9
13
|
FileDropImageSize,
|
|
10
14
|
FileDropImageTransform,
|
|
11
15
|
FileDropImageTransformOperation,
|
|
12
16
|
FileDropPresentation,
|
|
17
|
+
FileDropPresentationAction,
|
|
13
18
|
FileDropPresentationEntry,
|
|
14
19
|
FileDropPresentationItem,
|
|
15
20
|
FileDropPreview,
|
|
@@ -22,7 +27,44 @@ export function isDragLeavingCurrentTarget(
|
|
|
22
27
|
): boolean {
|
|
23
28
|
const nextTarget = event.relatedTarget;
|
|
24
29
|
|
|
25
|
-
return !(
|
|
30
|
+
return !(
|
|
31
|
+
nextTarget instanceof Node && event.currentTarget.contains(nextTarget)
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function createFileDropTargetHandlers({
|
|
36
|
+
enabled,
|
|
37
|
+
onDragOverChange,
|
|
38
|
+
onFiles,
|
|
39
|
+
}: Readonly<{
|
|
40
|
+
enabled: boolean;
|
|
41
|
+
onDragOverChange: (active: boolean) => void;
|
|
42
|
+
onFiles: (files: FileList | readonly File[] | undefined) => void;
|
|
43
|
+
}>): React.HTMLAttributes<HTMLDivElement> {
|
|
44
|
+
if (!enabled) {
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
onDragEnter: (event) => {
|
|
50
|
+
event.preventDefault();
|
|
51
|
+
onDragOverChange(true);
|
|
52
|
+
},
|
|
53
|
+
onDragLeave: (event) => {
|
|
54
|
+
if (isDragLeavingCurrentTarget(event)) {
|
|
55
|
+
onDragOverChange(false);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
onDragOver: (event) => {
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
onDragOverChange(true);
|
|
61
|
+
},
|
|
62
|
+
onDrop: (event) => {
|
|
63
|
+
event.preventDefault();
|
|
64
|
+
onDragOverChange(false);
|
|
65
|
+
onFiles(event.dataTransfer?.files);
|
|
66
|
+
},
|
|
67
|
+
};
|
|
26
68
|
}
|
|
27
69
|
|
|
28
70
|
export function getFileInputAccept(accept: string): string {
|
|
@@ -59,9 +101,12 @@ export function getFileInputAccept(accept: string): string {
|
|
|
59
101
|
export function normalizeImageRotation(
|
|
60
102
|
rotationDeg: number | undefined,
|
|
61
103
|
): 0 | 90 | 180 | 270 {
|
|
62
|
-
const normalized =
|
|
104
|
+
const normalized =
|
|
105
|
+
(((Math.round((rotationDeg ?? 0) / 90) * 90) % 360) + 360) % 360;
|
|
63
106
|
|
|
64
|
-
return normalized === 90 || normalized === 180 || normalized === 270
|
|
107
|
+
return normalized === 90 || normalized === 180 || normalized === 270
|
|
108
|
+
? normalized
|
|
109
|
+
: 0;
|
|
65
110
|
}
|
|
66
111
|
|
|
67
112
|
export function getImageTransformStyle(
|
|
@@ -144,13 +189,73 @@ export function getPresentationItemKey(
|
|
|
144
189
|
return item.id;
|
|
145
190
|
}
|
|
146
191
|
|
|
192
|
+
export function createFileDropItemView(
|
|
193
|
+
presentation: FileDropPresentation<string, unknown, unknown>,
|
|
194
|
+
requiresPreviewSource: boolean,
|
|
195
|
+
): Readonly<{
|
|
196
|
+
entries: readonly FileDropPresentationEntry[];
|
|
197
|
+
hasContent: boolean;
|
|
198
|
+
isListPresenter: boolean;
|
|
199
|
+
itemKeys: string[];
|
|
200
|
+
items: readonly FileDropPresentationItem[];
|
|
201
|
+
}> {
|
|
202
|
+
const entries: readonly FileDropPresentationEntry[] =
|
|
203
|
+
presentation.items.flatMap((item, sourceIndex) =>
|
|
204
|
+
requiresPreviewSource && !item.previewSrc
|
|
205
|
+
? []
|
|
206
|
+
: [
|
|
207
|
+
{
|
|
208
|
+
item,
|
|
209
|
+
key: getPresentationItemKey(item, sourceIndex),
|
|
210
|
+
sourceIndex,
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
entries,
|
|
217
|
+
hasContent: entries.length > 0,
|
|
218
|
+
isListPresenter:
|
|
219
|
+
presentation.presenter === "file-list" ||
|
|
220
|
+
presentation.presenter === "model-item",
|
|
221
|
+
itemKeys: entries.map((entry) => entry.key),
|
|
222
|
+
items: entries.map((entry) => entry.item),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function createFileDropActionModel(
|
|
227
|
+
actions: readonly FileDropPresentationAction[],
|
|
228
|
+
enabled: boolean,
|
|
229
|
+
): Readonly<{
|
|
230
|
+
options: readonly ActionControlObjectOption[];
|
|
231
|
+
states: Readonly<Record<string, ActionsControlItemState>>;
|
|
232
|
+
}> {
|
|
233
|
+
return {
|
|
234
|
+
options: actions.map(({ state: _state, ...action }) => action),
|
|
235
|
+
states: actions.reduce<Record<string, ActionsControlItemState>>(
|
|
236
|
+
(states, action) => {
|
|
237
|
+
if (!enabled) {
|
|
238
|
+
states[action.value] = { ...action.state, disabled: true };
|
|
239
|
+
} else if (action.state) {
|
|
240
|
+
states[action.value] = action.state;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return states;
|
|
244
|
+
},
|
|
245
|
+
{},
|
|
246
|
+
),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
147
250
|
export function reorderFileDropPresentationItems<AssetKind extends string>(
|
|
148
251
|
items: readonly FileDropPresentationItem<AssetKind>[],
|
|
149
252
|
visibleEntries: readonly FileDropPresentationEntry<AssetKind>[],
|
|
150
253
|
activeKey: string,
|
|
151
254
|
overKey: string,
|
|
152
255
|
): FileDropPresentationItem<AssetKind>[] | null {
|
|
153
|
-
const activeIndex = visibleEntries.findIndex(
|
|
256
|
+
const activeIndex = visibleEntries.findIndex(
|
|
257
|
+
(entry) => entry.key === activeKey,
|
|
258
|
+
);
|
|
154
259
|
const overIndex = visibleEntries.findIndex((entry) => entry.key === overKey);
|
|
155
260
|
|
|
156
261
|
if (activeIndex < 0 || overIndex < 0 || activeIndex === overIndex) {
|
|
@@ -225,7 +330,9 @@ export function createLegacyFileDropPresentation({
|
|
|
225
330
|
? "image-grid"
|
|
226
331
|
: "single-image";
|
|
227
332
|
const emptyTitle =
|
|
228
|
-
assetKind === "file"
|
|
333
|
+
assetKind === "file"
|
|
334
|
+
? "Click to upload a file"
|
|
335
|
+
: "Click to upload an image";
|
|
229
336
|
const dropTargetLabel = hasContent
|
|
230
337
|
? multiple
|
|
231
338
|
? assetKind === "file"
|
|
@@ -271,14 +378,29 @@ export function FileDropPlusGlyph({
|
|
|
271
378
|
viewBox="0 0 14 14"
|
|
272
379
|
xmlns="http://www.w3.org/2000/svg"
|
|
273
380
|
>
|
|
274
|
-
<path
|
|
275
|
-
|
|
381
|
+
<path
|
|
382
|
+
d="M7 2.5V11.5"
|
|
383
|
+
stroke="currentColor"
|
|
384
|
+
strokeLinecap="round"
|
|
385
|
+
strokeWidth="1"
|
|
386
|
+
/>
|
|
387
|
+
<path
|
|
388
|
+
d="M2.5 7H11.5"
|
|
389
|
+
stroke="currentColor"
|
|
390
|
+
strokeLinecap="round"
|
|
391
|
+
strokeWidth="1"
|
|
392
|
+
/>
|
|
276
393
|
</svg>
|
|
277
394
|
);
|
|
278
395
|
}
|
|
279
396
|
|
|
280
397
|
export const imageTransformActions = [
|
|
281
|
-
{
|
|
398
|
+
{
|
|
399
|
+
ariaLabel: "90° Right",
|
|
400
|
+
icon: "rotate-cw",
|
|
401
|
+
label: "90°",
|
|
402
|
+
value: "rotate-right",
|
|
403
|
+
},
|
|
282
404
|
{
|
|
283
405
|
ariaLabel: "Flip horizontal",
|
|
284
406
|
icon: "flip-horizontal",
|