Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
4062769
fix: Never submit fields without a value
afbora Jul 29, 2026
c8bf6fe
feat: New filelist field
distantnative Aug 13, 2026
d2d22c3
refact: Validator base element
afbora Aug 13, 2026
2a9c0d5
refact: Extract a base component for model lists
afbora Aug 13, 2026
35cd538
refact: Accept a blueprint list in the page create dialog
afbora Aug 13, 2026
e9bf6c3
feat: New pagelist field
afbora Aug 13, 2026
35364d1
feat: Add lab examples for the pagelist field
afbora Aug 13, 2026
ca39dfc
docs: Add missing `@since` tags
distantnative Aug 13, 2026
8c99215
refact: Rename to shared `models` prop
distantnative Aug 13, 2026
a828f98
refact: Separate ModelListField props and state
distantnative Aug 13, 2026
0067939
fix: Restore the invalid state of the list fields
afbora Aug 14, 2026
c4c60f4
test: Cover the blueprint fallbacks of the pagelist field
afbora Aug 14, 2026
3621703
fix: Remove unused input emit
distantnative Aug 14, 2026
833154c
fix: Render validators as `display: contents`
distantnative Aug 14, 2026
87a2a78
refact: Use `$options.type` for modellistfield
distantnative Aug 14, 2026
a3b6e68
fix: filelist field replace when upload false
distantnative Aug 14, 2026
bdfa80c
fix: Support label `link` in modellist fields
distantnative Aug 14, 2026
66f0f41
test: Fix lab example with initial state
distantnative Aug 14, 2026
0dea03a
feat: Rename files, pages and users fields to filepicker, pagepicker …
distantnative Aug 15, 2026
bc49923
fix: Breaking unit test after HtmlString changes
bastianallgeier Aug 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion config/blocks/gallery/gallery.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ preview: gallery
fields:
images:
label: field.blocks.gallery.images.label
type: files
type: filepicker
query: model.images
multiple: true
layout: cards
Expand Down
2 changes: 1 addition & 1 deletion config/blocks/image/image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ fields:
web: "{{ t('field.blocks.image.location.external') }}"
image:
label: field.blocks.image.name
type: files
type: filepicker
query: model.images
multiple: false
image:
Expand Down
4 changes: 2 additions & 2 deletions config/blocks/video/video.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ fields:
location: web
video:
label: field.blocks.video.name
type: files
type: filepicker
query: model.videos
multiple: false
# you might want to add a template for videos here
when:
location: kirby
poster:
label: field.blocks.video.poster
type: files
type: filepicker
query: model.images
multiple: false
image:
Expand Down
80 changes: 80 additions & 0 deletions panel/lab/components/fields/filelist/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

use Kirby\Toolkit\A;
use Kirby\Toolkit\Str;

$files = A::map(range(1, 7), function ($file) {
return [
'id' => 'photography/image-' . $file . '.jpg',
'image' => [
'src' => 'https://picsum.photos/800/600/?v=' . Str::random()
],
'info' => 'image',
'link' => '/pages/photography/files/image-' . $file . '.jpg',
'permissions' => [
'delete' => true,
'sort' => true
],
'template' => 'image',
'text' => 'image-' . $file . '.jpg',
'uuid' => 'file://' . Str::random(16)
];
});

// the columns of a table layout are resolved on the server,
// so the lab has to ship them the way `ModelListField` sends them
$columns = [
'image' => [
'label' => ' ',
'mobile' => true,
'type' => 'image',
'width' => 'var(--table-row-height)'
],
'title' => [
'label' => 'Title',
'mobile' => true,
'type' => 'url'
]
];

return [
'docs' => 'k-filelist-field',
'columns' => $columns,
'customColumns' => [
...$columns,
'alt' => [
'id' => 'alt',
'label' => 'Alt text',
'type' => 'text'
],
'template' => [
'id' => 'template',
'label' => 'Template',
'type' => 'text'
]
],
'endpoints' => [
'field' => 'pages/photography/fields/gallery'
],
'files' => $files,
'pagination' => [
'limit' => 7,
'offset' => 0,
'page' => 1,
'total' => 7
],
'paginated' => [
'limit' => 3,
'offset' => 0,
'page' => 1,
'total' => 7
],
'upload' => [
'accept' => 'image/*',
'api' => 'pages/photography/files',
'attributes' => [],
'max' => null,
'multiple' => true,
'preview' => []
]
];
200 changes: 200 additions & 0 deletions panel/lab/components/fields/filelist/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
<template>
<k-lab-form>
<k-lab-examples class="k-lab-field-examples">
<k-box theme="notice" icon="alert">
The examples below are rendered from static props. There is no model
behind them, so everything that talks to the field endpoint (search,
pagination, sorting, uploads and batch deletion) answers with a 404 here
and only works in a real model view.
</k-box>

<k-lab-example label="Default">
<k-filelist-field :initial="state(files)" label="Files" />
</k-lab-example>

<k-lab-example label="Help">
<k-filelist-field
:initial="state(files)"
help="Every file of this page"
label="Files"
/>
</k-lab-example>

<k-lab-example label="Empty">
<k-filelist-field
:initial="state([], { pagination: empty })"
label="Files"
/>
</k-lab-example>

<k-lab-example label="Empty with custom text">
<k-filelist-field
:initial="state([], { pagination: empty })"
empty="No images have been added yet"
label="Files"
/>
</k-lab-example>

<k-lab-example label="Invalid: fewer than min">
<k-filelist-field
:initial="state(files.slice(0, 1), { pagination: single })"
:min="2"
help="The label marks the field as invalid until a second file is added"
label="Files"
/>
</k-lab-example>

<k-lab-example label="Layout: cardlets">
<k-filelist-field
:initial="state(files)"
label="Files"
layout="cardlets"
/>
</k-lab-example>

<k-lab-example label="Layout: cards">
<k-filelist-field
:initial="state(files)"
label="Files"
layout="cards"
/>
</k-lab-example>

<k-lab-example label="Layout: cards, size small">
<k-filelist-field
:initial="state(files)"
label="Files"
layout="cards"
size="small"
/>
</k-lab-example>

<k-lab-example label="Layout: table">
<k-filelist-field
:initial="state(tableRows, { columns })"
label="Files"
layout="table"
/>
</k-lab-example>

<k-lab-example label="Layout: table with columns">
<k-filelist-field
:initial="state(tableRows, { columns: customColumns })"
label="Files"
layout="table"
/>
</k-lab-example>

<k-lab-example label="Link to another parent">
<k-filelist-field
:initial="state(files)"
label="Files"
link="/pages/photography"
/>
</k-lab-example>

<k-lab-example label="Pagination">
<k-filelist-field
:endpoints="endpoints"
:initial="state(files.slice(0, 3), { pagination: paginated })"
label="Files"
/>
</k-lab-example>

<k-lab-example label="Search">
<k-filelist-field
:endpoints="endpoints"
:initial="state(files)"
:searchable="true"
label="Files"
/>
</k-lab-example>

<k-lab-example label="Batch">
<k-filelist-field
:batch="true"
:endpoints="endpoints"
:initial="state(files)"
label="Files"
/>
</k-lab-example>

<k-lab-example label="Upload">
<k-filelist-field
:endpoints="endpoints"
:initial="state(files, { upload })"
label="Files"
/>
</k-lab-example>

<k-lab-example label="All options">
<k-filelist-field
:batch="true"
:endpoints="endpoints"
:initial="
state(files.slice(0, 3), {
pagination: paginated,
sortable: true,
upload
})
"
:searchable="true"
help="Search, batch select, sorting and uploads at once"
label="Files"
/>
</k-lab-example>
</k-lab-examples>
</k-lab-form>
</template>

<script>
export default {
props: {
columns: Object,
customColumns: Object,
endpoints: Object,
files: Array,
paginated: Object,
pagination: Object,
upload: Object
},
computed: {
empty() {
return { limit: 20, offset: 0, page: 1, total: 0 };
},
single() {
return { limit: 20, offset: 0, page: 1, total: 1 };
},
/**
* `ModelListField::columnsValues()` adds the cell values
* for the table layout to every entry
*/
tableRows() {
return this.files.map((file) => ({
...file,
alt: "Alt text for " + file.text,
title: {
text: file.text,
href: file.link
}
}));
}
},
methods: {
/**
* `FileListField::state()` sends the entries together with
* the columns, pagination, sorting and upload settings
*/
state(models, state = {}) {
return {
columns: {},
models,
pagination: this.pagination,
sortable: false,
upload: false,
...state
};
}
}
};
</script>
5 changes: 5 additions & 0 deletions panel/lab/components/fields/filepicker/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

return [
'docs' => 'k-filepicker-field',
];
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</k-box>

<k-lab-example label="Default">
<k-files-field
<k-filepicker-field
name="files"
label="files"
:value="value"
Expand All @@ -15,7 +15,7 @@
</k-lab-example>

<k-lab-example label="Autofocus">
<k-files-field
<k-filepicker-field
:autofocus="true"
label="files"
:value="value"
Expand All @@ -24,7 +24,7 @@
</k-lab-example>

<k-lab-example label="Required">
<k-files-field
<k-filepicker-field
label="files"
:required="true"
:value="value"
Expand All @@ -33,7 +33,7 @@
</k-lab-example>

<k-lab-example label="Placeholder">
<k-files-field
<k-filepicker-field
label="files"
:value="value"
placeholder="Placeholder text …"
Expand All @@ -42,7 +42,7 @@
</k-lab-example>

<k-lab-example label="Help">
<k-files-field
<k-filepicker-field
label="files"
:value="value"
help="This is some help text"
Expand All @@ -51,7 +51,7 @@
</k-lab-example>

<k-lab-example label="Before & After">
<k-files-field
<k-filepicker-field
label="files"
:value="value"
after="After"
Expand All @@ -61,7 +61,7 @@
</k-lab-example>

<k-lab-example label="Icon">
<k-files-field
<k-filepicker-field
label="files"
:value="value"
icon="edit"
Expand All @@ -70,7 +70,7 @@
</k-lab-example>

<k-lab-example label="Disabled">
<k-files-field
<k-filepicker-field
:disabled="true"
label="files"
:value="value"
Expand Down
Loading
Loading