Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0fe5d5f
fix: Never submit fields without a value
afbora Jul 29, 2026
7ff8681
feat: New filelist field
distantnative Aug 13, 2026
1053c64
refact: Validator base element
afbora Aug 13, 2026
ea22769
refact: Extract a base component for model lists
afbora Aug 13, 2026
8c30681
refact: Accept a blueprint list in the page create dialog
afbora Aug 13, 2026
3b0e708
feat: New pagelist field
afbora Aug 13, 2026
54549c0
feat: Add lab examples for the pagelist field
afbora Aug 13, 2026
6bdca32
docs: Add missing `@since` tags
distantnative Aug 13, 2026
f398ce5
refact: Rename to shared `models` prop
distantnative Aug 13, 2026
4d451af
refact: Separate ModelListField props and state
distantnative Aug 13, 2026
02577d1
fix: Restore the invalid state of the list fields
afbora Aug 14, 2026
464c9fe
test: Cover the blueprint fallbacks of the pagelist field
afbora Aug 14, 2026
3611cad
fix: Remove unused input emit
distantnative Aug 14, 2026
74fafb5
fix: Render validators as `display: contents`
distantnative Aug 14, 2026
99c1713
refact: Use `$options.type` for modellistfield
distantnative Aug 14, 2026
e53d554
fix: filelist field replace when upload false
distantnative Aug 14, 2026
bd5fcae
fix: Support label `link` in modellist fields
distantnative Aug 14, 2026
93a0f74
test: Fix lab example with initial state
distantnative Aug 14, 2026
66826f5
feat: Rename files, pages and users fields to filepicker, pagepicker …
distantnative Aug 15, 2026
0728cd0
fix: Breaking unit test after HtmlString changes
bastianallgeier Aug 17, 2026
5e19147
feat: New field.loaded event in ModelListField
bastianallgeier Aug 16, 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