playbook_ui 16.10.0.pre.alpha.typeaheaddraggablepills17453 → 16.10.0.pre.alpha.typeaheaddraggablepills17538
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.
- checksums.yaml +4 -4
- data/app/pb_kits/playbook/pb_draggable/utilities/touchDrag.ts +23 -5
- data/app/pb_kits/playbook/pb_file_upload/docs/_file_upload_remove_replace.html.erb +66 -0
- data/app/pb_kits/playbook/pb_file_upload/docs/_file_upload_remove_replace.jsx +53 -0
- data/app/pb_kits/playbook/pb_file_upload/docs/_file_upload_remove_replace.md +5 -0
- data/app/pb_kits/playbook/pb_file_upload/docs/example.yml +3 -1
- data/app/pb_kits/playbook/pb_file_upload/docs/index.js +1 -0
- data/dist/chunks/{_typeahead-C8XfPvOZ.js → _typeahead-DGtRB7T-.js} +1 -1
- data/dist/chunks/vendor.js +1 -1
- data/dist/playbook-rails-react-bindings.js +1 -1
- data/dist/playbook-rails.js +1 -1
- data/lib/playbook/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bca235e7dee62e26ea422c46150b0c5cb156eeddf2020efa6d4e6e00c81a3467
|
|
4
|
+
data.tar.gz: 9e4bc1641b834261e19e6190eb285bc90ce1552add02b46692275639955f900e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 71a8eb45f4aa9472fadaba3c0bbb606e8aef5c394499015c8a03b0b551b89fd9a96bf3e05c8af969e3ad3d9339c9273f549d791113a16faab9171f2921d329e3
|
|
7
|
+
data.tar.gz: a554ab79224396230c9703bc581ba133bc5c03f3899238114e98ae14500bae9eaeeca311897e0cc40f5933d76e8a38f62221e433fb19d553ffd7b46291594836
|
|
@@ -157,6 +157,8 @@ const runDragPointerMove = (
|
|
|
157
157
|
dragId: string,
|
|
158
158
|
container: string | undefined,
|
|
159
159
|
handlers: TouchDragHandlers,
|
|
160
|
+
// Typeahead pointer-drag only. Regular Draggable/touch keeps simple elementFromPoint.
|
|
161
|
+
useEnhancedHorizontalTargeting = false,
|
|
160
162
|
) => {
|
|
161
163
|
if (!state.dragging) {
|
|
162
164
|
const deltaX = clientX - state.startX;
|
|
@@ -168,11 +170,27 @@ const runDragPointerMove = (
|
|
|
168
170
|
handlers.onDragStart(dragId, container);
|
|
169
171
|
}
|
|
170
172
|
|
|
171
|
-
|
|
173
|
+
let targetDragId: string | null;
|
|
174
|
+
let targetContainer: string | undefined;
|
|
175
|
+
|
|
176
|
+
if (useEnhancedHorizontalTargeting) {
|
|
177
|
+
({ targetDragId, targetContainer } = resolvePointerDragTarget(clientX, clientY, dragId));
|
|
178
|
+
} else {
|
|
179
|
+
const elementBelow = typeof document.elementFromPoint === 'function'
|
|
180
|
+
? document.elementFromPoint(clientX, clientY)
|
|
181
|
+
: null;
|
|
182
|
+
targetDragId = getDragIdFromElement(elementBelow);
|
|
183
|
+
targetContainer = getContainerFromElement(elementBelow);
|
|
184
|
+
}
|
|
172
185
|
|
|
173
|
-
if (
|
|
174
|
-
|
|
175
|
-
|
|
186
|
+
if (useEnhancedHorizontalTargeting) {
|
|
187
|
+
if (!targetDragId || targetDragId === dragId) {
|
|
188
|
+
state.lastTargetDragId = null;
|
|
189
|
+
} else if (targetDragId !== state.lastTargetDragId) {
|
|
190
|
+
state.lastTargetDragId = targetDragId;
|
|
191
|
+
handlers.onDragEnter(targetDragId, targetContainer ?? container);
|
|
192
|
+
}
|
|
193
|
+
} else if (targetDragId && targetDragId !== dragId && targetDragId !== state.lastTargetDragId) {
|
|
176
194
|
state.lastTargetDragId = targetDragId;
|
|
177
195
|
handlers.onDragEnter(targetDragId, targetContainer ?? container);
|
|
178
196
|
}
|
|
@@ -337,7 +355,7 @@ export const bindMousePointerDrag = ({
|
|
|
337
355
|
const handleMouseMove = (event: MouseEvent) => {
|
|
338
356
|
if (!state.active) return;
|
|
339
357
|
|
|
340
|
-
if (runDragPointerMove(state, event.clientX, event.clientY, dragId, container, handlers)) {
|
|
358
|
+
if (runDragPointerMove(state, event.clientX, event.clientY, dragId, container, handlers, true)) {
|
|
341
359
|
event.preventDefault();
|
|
342
360
|
}
|
|
343
361
|
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<div id="file-upload-remove-replace">
|
|
2
|
+
<%= pb_rails("list", props: { id: "selected-files-list", margin_bottom: "sm" }) %>
|
|
3
|
+
|
|
4
|
+
<%= pb_rails("file_upload", props: {
|
|
5
|
+
id: "remove_replace",
|
|
6
|
+
input_options: { multiple: true },
|
|
7
|
+
}) %>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<template id="file-list-item-template">
|
|
11
|
+
<%= pb_rails("list/item") do %>
|
|
12
|
+
<%= pb_rails("flex", props: { align: "center", justify: "between", width: "100%" }) do %>
|
|
13
|
+
<%= pb_rails("body", props: { data: { file_name: true } }) %>
|
|
14
|
+
<%= pb_rails("circle_icon_button", props: {
|
|
15
|
+
icon: "times",
|
|
16
|
+
variant: "secondary",
|
|
17
|
+
input_options: { classname: "remove-file-button" },
|
|
18
|
+
}) %>
|
|
19
|
+
<% end %>
|
|
20
|
+
<% end %>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<%= javascript_tag defer: "defer" do %>
|
|
24
|
+
document.addEventListener("DOMContentLoaded", function () {
|
|
25
|
+
const fileInput = document.getElementById("upload-remove_replace")
|
|
26
|
+
const list = document.getElementById("selected-files-list")
|
|
27
|
+
const template = document.getElementById("file-list-item-template")
|
|
28
|
+
let files = []
|
|
29
|
+
|
|
30
|
+
if (!fileInput || !list || !template) return
|
|
31
|
+
|
|
32
|
+
function syncInput() {
|
|
33
|
+
const dataTransfer = new DataTransfer()
|
|
34
|
+
files.forEach((file) => dataTransfer.items.add(file))
|
|
35
|
+
fileInput.files = dataTransfer.files
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function render() {
|
|
39
|
+
list.innerHTML = ""
|
|
40
|
+
|
|
41
|
+
files.forEach((file, index) => {
|
|
42
|
+
const clone = template.content.cloneNode(true)
|
|
43
|
+
const nameElement = clone.querySelector("[data-file-name]")
|
|
44
|
+
const removeButton = clone.querySelector(".remove-file-button")
|
|
45
|
+
|
|
46
|
+
nameElement.textContent = file.name
|
|
47
|
+
removeButton.addEventListener("click", function (event) {
|
|
48
|
+
event.preventDefault()
|
|
49
|
+
event.stopPropagation()
|
|
50
|
+
files = files.filter((_, fileIndex) => fileIndex !== index)
|
|
51
|
+
syncInput()
|
|
52
|
+
render()
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
list.appendChild(clone)
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fileInput.addEventListener("change", function () {
|
|
60
|
+
const newFiles = Array.from(fileInput.files)
|
|
61
|
+
files = [...files, ...newFiles]
|
|
62
|
+
syncInput()
|
|
63
|
+
render()
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
<% end %>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
|
|
2
|
+
import React, { useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import Body from '../../pb_body/_body'
|
|
5
|
+
import CircleIconButton from '../../pb_circle_icon_button/_circle_icon_button'
|
|
6
|
+
import FileUpload from '../../pb_file_upload/_file_upload'
|
|
7
|
+
import Flex from '../../pb_flex/_flex'
|
|
8
|
+
import List from '../../pb_list/_list'
|
|
9
|
+
import ListItem from '../../pb_list/_list_item'
|
|
10
|
+
|
|
11
|
+
const FileUploadRemoveReplace = (props) => {
|
|
12
|
+
const [filesToUpload, setFilesToUpload] = useState([])
|
|
13
|
+
|
|
14
|
+
const handleOnFilesAccepted = (files) => {
|
|
15
|
+
setFilesToUpload([...filesToUpload, ...files])
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const handleRemoveFile = (index) => {
|
|
19
|
+
setFilesToUpload(filesToUpload.filter((_, fileIndex) => fileIndex !== index))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div>
|
|
24
|
+
{filesToUpload.length > 0 && (
|
|
25
|
+
<List marginBottom="sm">
|
|
26
|
+
{filesToUpload.map((file, index) => (
|
|
27
|
+
<ListItem key={`${file.name}-${index}`}>
|
|
28
|
+
<Flex
|
|
29
|
+
align="center"
|
|
30
|
+
justify="between"
|
|
31
|
+
width="100%"
|
|
32
|
+
>
|
|
33
|
+
<Body text={file.name} />
|
|
34
|
+
<CircleIconButton
|
|
35
|
+
aria={{ label: `Remove ${file.name}` }}
|
|
36
|
+
icon="times"
|
|
37
|
+
onClick={() => handleRemoveFile(index)}
|
|
38
|
+
variant="secondary"
|
|
39
|
+
/>
|
|
40
|
+
</Flex>
|
|
41
|
+
</ListItem>
|
|
42
|
+
))}
|
|
43
|
+
</List>
|
|
44
|
+
)}
|
|
45
|
+
<FileUpload
|
|
46
|
+
onFilesAccepted={handleOnFilesAccepted}
|
|
47
|
+
{...props}
|
|
48
|
+
/>
|
|
49
|
+
</div>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export default FileUploadRemoveReplace
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
The File Upload kit does not include built-in remove or replace behavior. Manage selected files in application state and compose the kit with other Playbook components.
|
|
2
|
+
|
|
3
|
+
**Remove** — Track accepted files in state and filter out the file to remove. In React, pair `List` with `CircleIconButton`. In Rails, listen for `change` on the file input, keep files in a JavaScript array, and use `DataTransfer` to sync the input when a file is removed.
|
|
4
|
+
|
|
5
|
+
**Replace** — Selecting a new file on the native Rails input replaces the current selection. For single-file React workflows, set state to the newly accepted files instead of appending: `setFilesToUpload(files)`.
|
|
@@ -4,6 +4,7 @@ examples:
|
|
|
4
4
|
- file_upload_default: File Upload
|
|
5
5
|
- file_upload_custom: Custom
|
|
6
6
|
- file_upload_error: Error
|
|
7
|
+
- file_upload_remove_replace: Remove and Replace
|
|
7
8
|
|
|
8
9
|
react:
|
|
9
10
|
- file_upload_default: Default List of files to upload
|
|
@@ -11,4 +12,5 @@ examples:
|
|
|
11
12
|
- file_upload_custom_message: Add a custom message
|
|
12
13
|
- file_upload_custom_description: Add your one accepted files description
|
|
13
14
|
- file_upload_max_size: Set a file size limit
|
|
14
|
-
- file_upload_error: Error
|
|
15
|
+
- file_upload_error: Error
|
|
16
|
+
- file_upload_remove_replace: Remove and Replace
|
|
@@ -4,3 +4,4 @@ export { default as FileUploadCustomMessage } from './_file_upload_custom_messag
|
|
|
4
4
|
export { default as FileUploadCustomDescription } from './_file_upload_custom_description.jsx'
|
|
5
5
|
export { default as FileUploadMaxSize } from './_file_upload_max_size.jsx'
|
|
6
6
|
export { default as FileUploadError } from './_file_upload_error.jsx'
|
|
7
|
+
export { default as FileUploadRemoveReplace } from './_file_upload_remove_replace.jsx'
|