phlex-reactive 0.16.6 → 0.16.7
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/CHANGELOG.md +23 -1
- data/README.md +16 -1
- data/app/javascript/phlex/reactive/core.min.js +6 -6
- data/app/javascript/phlex/reactive/core.min.js.map +3 -3
- data/app/javascript/phlex/reactive/reactive_controller.min.js +4 -4
- data/app/javascript/phlex/reactive/reactive_controller.min.js.map +3 -3
- data/app/javascript/phlex/reactive/runtime.js +46 -12
- data/lib/phlex/reactive/js.rb +25 -2
- data/lib/phlex/reactive/version.rb +1 -1
- metadata +1 -1
|
@@ -1490,16 +1490,36 @@ function previewFile(input, args, resolveTargets) {
|
|
|
1490
1490
|
// `invalid` event fired into the app's own listeners.
|
|
1491
1491
|
const files = [...(args.valid === true && input.validity?.valid === false ? [] : (input.files ?? []))]
|
|
1492
1492
|
const accepts = previewAccepts(input, args.accept)
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1493
|
+
// Size is read BEFORE an object URL exists, so an over-size file never gets one.
|
|
1494
|
+
const oversize = (f) => Number.isInteger(args.max_bytes) && f.size > args.max_bytes
|
|
1495
|
+
const rejected = []
|
|
1496
|
+
let file
|
|
1497
|
+
if (args.per_file === true && input.multiple) {
|
|
1498
|
+
// Several files (issue #439): every file is checked, accept then
|
|
1499
|
+
// max_bytes. The refused ones leave input.files here, synchronously, so an
|
|
1500
|
+
// action bound after the op on the same event posts only the kept ones.
|
|
1501
|
+
const kept = files.filter((f, index) => {
|
|
1502
|
+
const reason = typeof f?.type === "string" && accepts(f) ? oversize(f) && "size" : "type"
|
|
1503
|
+
if (reason) rejected.push({ reason, file: f, index })
|
|
1504
|
+
return !reason
|
|
1505
|
+
})
|
|
1506
|
+
// remaining: the files the input keeps, and so the ones an action posts.
|
|
1507
|
+
const remaining = rejected.length > 0 && !keepFiles(input, kept) ? files.length : kept.length
|
|
1508
|
+
for (const detail of rejected) detail.remaining = remaining
|
|
1509
|
+
file = kept.find((f) => f.type.startsWith("image/"))
|
|
1510
|
+
} else {
|
|
1511
|
+
const listed = files.filter((f) => typeof f?.type === "string" && accepts(f))
|
|
1512
|
+
// Only an image is painted: a listed PDF clears the preview, unrefused.
|
|
1513
|
+
file = listed.find((f) => f.type.startsWith("image/"))
|
|
1514
|
+
// A refused file (issue #391) clears like an empty pick and says why. The
|
|
1515
|
+
// input keeps its files and its validity: the page owns the reset.
|
|
1516
|
+
let reason = files.length > 0 && listed.length === 0 ? "type" : null
|
|
1517
|
+
if (file && oversize(file)) reason = "size"
|
|
1518
|
+
if (reason) {
|
|
1519
|
+
rejected.push({ reason, file: reason === "size" ? file : files[0] })
|
|
1520
|
+
file = null
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1503
1523
|
forgetPreview(img)
|
|
1504
1524
|
if (file) {
|
|
1505
1525
|
const url = URL.createObjectURL(file)
|
|
@@ -1516,9 +1536,23 @@ function previewFile(input, args, resolveTargets) {
|
|
|
1516
1536
|
if (args.name != null) {
|
|
1517
1537
|
for (const node of resolveTargets({ ...args, to: args.name })) node.textContent = file ? file.name : ""
|
|
1518
1538
|
}
|
|
1519
|
-
|
|
1520
|
-
|
|
1539
|
+
for (const detail of rejected) input.dispatchEvent(new CustomEvent("reactive:file-rejected", { bubbles: true, detail }))
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// Rebuild input.files from the kept files (issue #439); true once it did. A
|
|
1543
|
+
// DataTransfer is the one way to make a FileList; without one the refused
|
|
1544
|
+
// files stay in the input (the rejections still fire), and a verbose root is told.
|
|
1545
|
+
function keepFiles(input, kept) {
|
|
1546
|
+
if (typeof DataTransfer !== "function") {
|
|
1547
|
+
if (input.closest?.('[data-reactive-verbose="true"], [data-reactive-debug="true"]')) {
|
|
1548
|
+
warnPreview("per_file: has no DataTransfer to drop the refused files from the input")
|
|
1549
|
+
}
|
|
1550
|
+
return false
|
|
1521
1551
|
}
|
|
1552
|
+
const transfer = new DataTransfer()
|
|
1553
|
+
for (const f of kept) transfer.items.add(f)
|
|
1554
|
+
input.files = transfer.files
|
|
1555
|
+
return true
|
|
1522
1556
|
}
|
|
1523
1557
|
|
|
1524
1558
|
// Which files the page takes: an image by default. accept: true reads the
|
data/lib/phlex/reactive/js.rb
CHANGED
|
@@ -329,8 +329,21 @@ module Phlex
|
|
|
329
329
|
# input(type: "file", id: "photo-file", accept: "image/png,image/jpeg",
|
|
330
330
|
# **on_client(:change, js.preview_file("#photo-file", into: "#photo",
|
|
331
331
|
# wrap: "#photo-frame", max_bytes: 2_000_000, accept: true)))
|
|
332
|
-
|
|
333
|
-
|
|
332
|
+
#
|
|
333
|
+
# Several files (issue #439): `per_file: true` on an input with
|
|
334
|
+
# `multiple` checks EVERY file (accept, then max_bytes), rebuilds
|
|
335
|
+
# input.files from the accepted ones (synchronously, so an
|
|
336
|
+
# `on(:action, event: "change")` mixed in AFTER the op posts only those),
|
|
337
|
+
# dispatches one `reactive:file-rejected` per refused file
|
|
338
|
+
# (detail.reason, file, index — its place in the pick — and remaining,
|
|
339
|
+
# how many files the input keeps), and paints the first accepted image.
|
|
340
|
+
# A single input, or per_file: omitted, keeps the behaviour above.
|
|
341
|
+
# input(type: "file", id: "pick", name: "file", multiple: true, accept: "image/png",
|
|
342
|
+
# **mix(on_client(:change, js.preview_file("#pick", into: "#shot", max_bytes: 1_000_000,
|
|
343
|
+
# accept: true, per_file: true)), on(:upload, event: "change")))
|
|
344
|
+
|
|
345
|
+
def preview_file(to, into:, name: nil, global: false, valid: false, wrap: nil, max_bytes: nil, accept: nil,
|
|
346
|
+
per_file: false)
|
|
334
347
|
if [:root, ROOT_SENTINEL].include?(to)
|
|
335
348
|
raise ArgumentError,
|
|
336
349
|
"#{self.class}: preview_file reads a file input, not the component root — " \
|
|
@@ -344,6 +357,7 @@ module Phlex
|
|
|
344
357
|
args["wrap"] = preview_selector(:wrap, wrap) unless wrap.nil?
|
|
345
358
|
args["max_bytes"] = preview_max_bytes(max_bytes) unless max_bytes.nil?
|
|
346
359
|
args["accept"] = preview_accept(accept) unless accept.nil? || accept == false
|
|
360
|
+
args["per_file"] = true if preview_per_file(per_file)
|
|
347
361
|
append("preview_file", args.freeze)
|
|
348
362
|
end
|
|
349
363
|
|
|
@@ -560,6 +574,15 @@ module Phlex
|
|
|
560
574
|
"#{self.class}: preview_file max_bytes: must be a positive Integer, got #{bytes.inspect}"
|
|
561
575
|
end
|
|
562
576
|
|
|
577
|
+
# per_file: is a strict boolean (nil, like the other keywords, means
|
|
578
|
+
# omitted): a typo'd truthy value must not change which files an upload posts.
|
|
579
|
+
def preview_per_file(per_file)
|
|
580
|
+
return per_file == true if [true, false, nil].include?(per_file)
|
|
581
|
+
|
|
582
|
+
raise ArgumentError,
|
|
583
|
+
"#{self.class}: preview_file per_file: must be true, false or nil, got #{per_file.inspect}"
|
|
584
|
+
end
|
|
585
|
+
|
|
563
586
|
# true reads the input's accept attribute; a String is the list itself.
|
|
564
587
|
def preview_accept(accept)
|
|
565
588
|
return accept if accept == true || (accept.is_a?(String) && !accept.strip.empty?)
|