@zeyos/zx 4.4.0 → 5.0.0
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/CHANGELOG.md +283 -0
- package/README.md +42 -10
- package/dist/types/components/activity-list/activity-list.d.ts +22 -0
- package/dist/types/components/app-sidebar/app-sidebar.d.ts +5 -3
- package/dist/types/components/avatar/avatar.d.ts +46 -7
- package/dist/types/components/badge/badge.d.ts +21 -1
- package/dist/types/components/button/button.d.ts +15 -0
- package/dist/types/components/chart/chart.d.ts +10 -0
- package/dist/types/components/checklist/checklist.d.ts +9 -0
- package/dist/types/components/code-editor/code-editor.d.ts +6 -17
- package/dist/types/components/datebox/date-range-box.d.ts +5 -1
- package/dist/types/components/datebox/datebox.d.ts +7 -2
- package/dist/types/components/dialog/dialog.d.ts +46 -6
- package/dist/types/components/dropdown/dropdown.d.ts +2 -2
- package/dist/types/components/entity-ref/entity-ref.d.ts +2 -2
- package/dist/types/components/field/field.d.ts +24 -2
- package/dist/types/components/fieldset/fieldset.d.ts +19 -1
- package/dist/types/components/file-item/file-item.d.ts +34 -6
- package/dist/types/components/form/form.d.ts +12 -1
- package/dist/types/components/kanban-view/kanban-card.d.ts +8 -0
- package/dist/types/components/launcher/launcher.d.ts +260 -15
- package/dist/types/components/list-toolbar/list-toolbar.d.ts +1 -1
- package/dist/types/components/menu-button/menu-button.d.ts +33 -5
- package/dist/types/components/message/message.d.ts +119 -17
- package/dist/types/components/modal/modal.d.ts +13 -2
- package/dist/types/components/select/color.d.ts +12 -0
- package/dist/types/components/select/glyphs.d.ts +124 -0
- package/dist/types/components/select/select.d.ts +11 -3
- package/dist/types/components/stat-tile/stat-tile.d.ts +16 -5
- package/dist/types/components/tabbox/tabbox.d.ts +9 -0
- package/dist/types/components/table/table.d.ts +46 -1
- package/dist/types/components/tag-picker/tag-picker.d.ts +21 -4
- package/dist/types/components/tree/tree.d.ts +13 -0
- package/dist/types/core/breakpoint.d.ts +1 -1
- package/dist/types/core/icons.d.ts +23 -25
- package/dist/types/core/motion.d.ts +59 -0
- package/dist/types/core/overlay-host.d.ts +15 -1
- package/dist/types/index.d.ts +5 -3
- package/dist/types/zeyos/connect.d.ts +6 -0
- package/dist/types/zeyos/form.d.ts +50 -9
- package/dist/types/zeyos/index.d.ts +1 -1
- package/dist/types/zeyos/query.d.ts +79 -0
- package/dist/types/zeyos/schema.d.ts +24 -1
- package/dist/types/zeyos/table.d.ts +100 -14
- package/dist/types/zeyos/view.d.ts +10 -0
- package/dist/zx-zeyos.esm.js +1621 -443
- package/dist/zx-zeyos.esm.min.js +53 -5
- package/dist/zx.css +1411 -660
- package/dist/zx.esm.js +1976 -703
- package/dist/zx.esm.min.js +56 -8
- package/dist/zx.global.js +1978 -705
- package/dist/zx.global.min.js +56 -8
- package/dist/zx.min.css +1 -1
- package/docs/api.json +219 -34
- package/docs/llms.md +472 -110
- package/docs/llms.txt +3 -3
- package/package.json +1 -1
- package/src/components/account-menu/account-menu.css +35 -10
- package/src/components/activity-item/activity-item.css +1 -1
- package/src/components/activity-item/activity-item.js +3 -0
- package/src/components/activity-list/activity-list.css +38 -3
- package/src/components/activity-list/activity-list.js +34 -0
- package/src/components/app-icon/app-icon.css +1 -1
- package/src/components/app-rail/app-rail.css +40 -79
- package/src/components/app-sidebar/app-sidebar.css +109 -174
- package/src/components/app-sidebar/app-sidebar.js +21 -6
- package/src/components/aurora/aurora.css +5 -3
- package/src/components/avatar/avatar.css +32 -7
- package/src/components/avatar/avatar.js +72 -8
- package/src/components/badge/badge.css +54 -2
- package/src/components/badge/badge.js +29 -2
- package/src/components/button/button.css +67 -9
- package/src/components/button/button.js +38 -0
- package/src/components/calendar/calendar.css +46 -46
- package/src/components/card/card.css +2 -2
- package/src/components/card-view/card-view.css +9 -7
- package/src/components/chart/chart.css +4 -1
- package/src/components/chart/chart.js +12 -0
- package/src/components/check-button/check-button.css +10 -2
- package/src/components/checklist/checklist.css +9 -2
- package/src/components/checklist/checklist.js +11 -5
- package/src/components/checklist/field-adapter.js +3 -1
- package/src/components/code-editor/code-editor.css +6 -5
- package/src/components/code-editor/code-editor.js +19 -1
- package/src/components/context-menu/context-menu.css +9 -4
- package/src/components/copy/copy-button.css +6 -0
- package/src/components/copy/copy-input.css +8 -3
- package/src/components/data-filter/data-filter.css +5 -4
- package/src/components/date-picker/date-picker.css +6 -6
- package/src/components/date-picker/date-range-picker.css +3 -3
- package/src/components/date-picker/time-picker.css +1 -1
- package/src/components/datebox/date-range-box.css +79 -2
- package/src/components/datebox/date-range-box.js +13 -2
- package/src/components/datebox/datebox.css +79 -2
- package/src/components/datebox/datebox.js +13 -2
- package/src/components/dialog/dialog.css +26 -0
- package/src/components/dialog/dialog.js +99 -17
- package/src/components/dropdown/dropdown.css +1 -1
- package/src/components/dropdown/dropdown.js +5 -3
- package/src/components/empty-state/empty-state.css +2 -2
- package/src/components/entity-ref/entity-ref.css +1 -1
- package/src/components/entity-ref/entity-ref.js +1 -1
- package/src/components/field/field.css +28 -9
- package/src/components/field/field.js +68 -26
- package/src/components/field-upload/field-upload.css +1 -1
- package/src/components/fieldset/fieldset.css +5 -3
- package/src/components/fieldset/fieldset.js +28 -5
- package/src/components/file-item/file-item.js +144 -10
- package/src/components/filter/filter.js +7 -3
- package/src/components/filter-panel/filter-panel.css +1 -1
- package/src/components/finder/finder.css +15 -13
- package/src/components/form/form.css +5 -4
- package/src/components/form/form.js +11 -2
- package/src/components/grid/grid.css +1 -1
- package/src/components/groupbox/groupbox.css +1 -1
- package/src/components/kanban-view/kanban-card.js +11 -3
- package/src/components/kanban-view/kanban-view.css +39 -15
- package/src/components/kanban-view/kanban-view.js +4 -4
- package/src/components/launcher/launcher.css +107 -25
- package/src/components/launcher/launcher.js +503 -41
- package/src/components/list-toolbar/list-toolbar.css +13 -8
- package/src/components/list-toolbar/list-toolbar.js +1 -1
- package/src/components/master-panel/master-panel.css +2 -0
- package/src/components/menu-button/menu-button.css +9 -4
- package/src/components/menu-button/menu-button.js +65 -9
- package/src/components/message/message.css +33 -3
- package/src/components/message/message.js +217 -15
- package/src/components/modal/modal.js +18 -3
- package/src/components/navigation-bar/navigation-bar.css +18 -9
- package/src/components/number-field/field-adapter.js +1 -0
- package/src/components/number-field/number-field.css +70 -0
- package/src/components/number-field/number-field.js +2 -0
- package/src/components/pagination/pagination.css +8 -6
- package/src/components/panel/panel.css +1 -1
- package/src/components/rating/rating.css +7 -0
- package/src/components/select/color.js +22 -0
- package/src/components/select/field-adapter.js +2 -0
- package/src/components/select/glyphs.js +165 -0
- package/src/components/select/select.css +138 -45
- package/src/components/select/select.js +83 -47
- package/src/components/sheet/sheet.css +11 -8
- package/src/components/slider/slider.css +16 -2
- package/src/components/slider/slider.js +1 -1
- package/src/components/sort-control/sort-control.css +4 -1
- package/src/components/stat-tile/stat-tile.css +41 -46
- package/src/components/stat-tile/stat-tile.js +17 -6
- package/src/components/tabbox/tabbox.css +32 -27
- package/src/components/tabbox/tabbox.js +5 -1
- package/src/components/table/table.css +104 -29
- package/src/components/table/table.js +90 -16
- package/src/components/tag-picker/tag-picker.css +83 -5
- package/src/components/tag-picker/tag-picker.js +25 -8
- package/src/components/timebox/timebox.css +22 -2
- package/src/components/toggle/toggle.css +12 -1
- package/src/components/toolbar/toolbar.js +2 -2
- package/src/components/tooltip/tooltip.css +4 -0
- package/src/components/tooltip/tooltip.js +10 -3
- package/src/components/tree/tree.css +25 -16
- package/src/components/tree/tree.js +31 -0
- package/src/components/value-list/value-list.css +3 -3
- package/src/components/view/record-view.js +1 -1
- package/src/core/breakpoint.js +1 -1
- package/src/core/icons.js +193 -60
- package/src/core/motion.js +147 -0
- package/src/core/overlay-host.js +21 -2
- package/src/index.js +5 -3
- package/src/internal/app-rail.js +1 -2
- package/src/zeyos/connect.js +6 -2
- package/src/zeyos/form.js +67 -27
- package/src/zeyos/index.js +1 -1
- package/src/zeyos/query.js +153 -0
- package/src/zeyos/schema.js +144 -27
- package/src/zeyos/table.js +68 -5
- package/src/zeyos/view.js +6 -0
- package/styles/base.css +56 -0
- package/styles/icons.css +11 -1
- package/styles/tokens/dark.css +113 -37
- package/styles/tokens/global.css +121 -27
- package/styles/tokens/semantic.css +100 -31
- package/styles/tokens/themes.css +7 -6
- package/styles/zx.css +0 -1
- package/styles/tokens/density.css +0 -9
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Component } from '../../core/component.js';
|
|
3
3
|
import { h, restoreTarget, snapshotTarget } from '../../core/dom.js';
|
|
4
4
|
import { formatFileSize } from '../../core/format.js';
|
|
5
|
+
import { getIconConfig, iconNames } from '../../core/icons.js';
|
|
5
6
|
import { isElement } from '../../core/util.js';
|
|
6
7
|
import { badge } from '../badge/badge.js';
|
|
7
8
|
import {
|
|
@@ -20,9 +21,13 @@ import {
|
|
|
20
21
|
* @property {unknown} [id=null] Application-owned file identifier included in events.
|
|
21
22
|
* @property {string} [name=''] File name.
|
|
22
23
|
* @property {number|string|null} [size=null] Byte count; invalid and negative values are omitted.
|
|
23
|
-
* @property {string} [mime=''] MIME type.
|
|
24
|
+
* @property {string} [mime=''] MIME type. Shown as a readable type ("PDF", "Image"), never as
|
|
25
|
+
* the raw string, and used to pick the icon.
|
|
26
|
+
* @property {string|null} [typeLabel=null] Type shown beside the size; null derives it from the
|
|
27
|
+
* MIME type (or the name's extension when the MIME type is unfamiliar), an empty string hides it.
|
|
24
28
|
* @property {EntityRefContent} [subtitle=null] Optional supporting text before generated metadata.
|
|
25
|
-
* @property {EntityRefIcon} [icon='file'] File icon name, node, component, or lazy renderer.
|
|
29
|
+
* @property {EntityRefIcon} [icon='file'] File icon name, node, component, or lazy renderer. The
|
|
30
|
+
* generic `'file'` glyph gives way to one for the MIME type when the active icon set has it.
|
|
26
31
|
* @property {EntityRefMetadata[]} [metadata=[]] Additional labelled metadata.
|
|
27
32
|
* @property {EntityRefLinkValue} [link=null] Optional safe native file/download link.
|
|
28
33
|
* @property {EntityRefActionEntry[]} [actions=[]] File actions; application policy stays external.
|
|
@@ -30,7 +35,7 @@ import {
|
|
|
30
35
|
* @property {string|null} [statusLabel=null] Overrides the generated state label; an empty string hides it.
|
|
31
36
|
* @property {number|null} [progress=null] Completed percentage, clamped to 0–100.
|
|
32
37
|
* @property {boolean} [indeterminate=false] Whether active progress has no known amount.
|
|
33
|
-
* @property {'sm'|'md'|'lg'} [
|
|
38
|
+
* @property {'sm'|'md'|'lg'} [identitySize='md'] Size of the file's icon and name.
|
|
34
39
|
* @property {boolean} [wrap=false] Whether long identity text may wrap.
|
|
35
40
|
* @property {string} [actionsLabel='File actions'] Accessible name for secondary actions.
|
|
36
41
|
* @property {string} [locale] Locale passed to `formatFileSize`.
|
|
@@ -46,6 +51,7 @@ import {
|
|
|
46
51
|
* @property {string} name File name.
|
|
47
52
|
* @property {number|null} size Byte count.
|
|
48
53
|
* @property {string} mime MIME type.
|
|
54
|
+
* @property {string|null} typeLabel Explicit type label, or null for the one derived from `mime`.
|
|
49
55
|
* @property {EntityRefContent} subtitle Supporting content.
|
|
50
56
|
* @property {EntityRefIcon} icon File visual.
|
|
51
57
|
* @property {EntityRefMetadata[]} metadata Additional metadata.
|
|
@@ -55,7 +61,7 @@ import {
|
|
|
55
61
|
* @property {string|null} statusLabel Explicit state label, or null for the generated label.
|
|
56
62
|
* @property {number|null} progress Completed percentage.
|
|
57
63
|
* @property {boolean} indeterminate Unknown-progress state.
|
|
58
|
-
* @property {'sm'|'md'|'lg'}
|
|
64
|
+
* @property {'sm'|'md'|'lg'} identitySize Size of the file's icon and name.
|
|
59
65
|
* @property {boolean} wrap Wrapping state.
|
|
60
66
|
* @property {string} actionsLabel Action-group label.
|
|
61
67
|
* @property {string|undefined} locale File-size locale.
|
|
@@ -91,6 +97,7 @@ export class FileItem extends Component {
|
|
|
91
97
|
name: '',
|
|
92
98
|
size: null,
|
|
93
99
|
mime: '',
|
|
100
|
+
typeLabel: null,
|
|
94
101
|
subtitle: null,
|
|
95
102
|
icon: 'file',
|
|
96
103
|
metadata: [],
|
|
@@ -100,7 +107,7 @@ export class FileItem extends Component {
|
|
|
100
107
|
statusLabel: null,
|
|
101
108
|
progress: null,
|
|
102
109
|
indeterminate: false,
|
|
103
|
-
|
|
110
|
+
identitySize: 'md',
|
|
104
111
|
wrap: false,
|
|
105
112
|
actionsLabel: 'File actions'
|
|
106
113
|
};
|
|
@@ -207,7 +214,7 @@ export class FileItem extends Component {
|
|
|
207
214
|
const track = /** @type {HTMLElement} */ (this.refs.progressTrack);
|
|
208
215
|
const fill = /** @type {HTMLElement} */ (this.refs.progressFill);
|
|
209
216
|
root.dataset.status = state.status;
|
|
210
|
-
root.dataset.
|
|
217
|
+
root.dataset.identitySize = state.identitySize;
|
|
211
218
|
this._entity.set(entityOptions(state));
|
|
212
219
|
|
|
213
220
|
const label = fileStatusLabel(state);
|
|
@@ -282,6 +289,7 @@ export function normalizeFileItem(values = {}) {
|
|
|
282
289
|
name: String(source.name ?? ''),
|
|
283
290
|
size: rawSize !== null && Number.isFinite(rawSize) && rawSize >= 0 ? rawSize : null,
|
|
284
291
|
mime: String(source.mime ?? ''),
|
|
292
|
+
typeLabel: source.typeLabel == null ? null : String(source.typeLabel),
|
|
285
293
|
subtitle: normalizeFileContent(source.subtitle),
|
|
286
294
|
icon: source.icon === undefined ? 'file' : source.icon,
|
|
287
295
|
metadata: normalizeEntityRefMetadata(source.metadata),
|
|
@@ -292,7 +300,7 @@ export function normalizeFileItem(values = {}) {
|
|
|
292
300
|
progress: rawProgress === null || !Number.isFinite(rawProgress)
|
|
293
301
|
? null : Math.min(100, Math.max(0, rawProgress)),
|
|
294
302
|
indeterminate: Boolean(source.indeterminate),
|
|
295
|
-
|
|
303
|
+
identitySize: source.identitySize === 'sm' || source.identitySize === 'lg' ? source.identitySize : 'md',
|
|
296
304
|
wrap: Boolean(source.wrap),
|
|
297
305
|
actionsLabel: String(source.actionsLabel || 'File actions'),
|
|
298
306
|
locale: normalizeLocale(source.locale)
|
|
@@ -302,7 +310,8 @@ export function normalizeFileItem(values = {}) {
|
|
|
302
310
|
/** @param {FileItemState} state @returns {import('../entity-ref/entity-ref.js').EntityRefOptions} */
|
|
303
311
|
function entityOptions(state) {
|
|
304
312
|
const metadata = [];
|
|
305
|
-
|
|
313
|
+
const type = state.typeLabel ?? fileTypeLabel(state.mime, state.name);
|
|
314
|
+
if (type) metadata.push({ label: 'Type', value: type, showLabel: false });
|
|
306
315
|
if (state.size !== null) {
|
|
307
316
|
metadata.push({
|
|
308
317
|
label: 'Size',
|
|
@@ -315,16 +324,141 @@ function entityOptions(state) {
|
|
|
315
324
|
id: state.id,
|
|
316
325
|
title: state.name,
|
|
317
326
|
subtitle: state.subtitle,
|
|
318
|
-
icon: state.icon,
|
|
327
|
+
icon: state.icon === 'file' ? fileTypeIcon(state.mime) : state.icon,
|
|
319
328
|
metadata,
|
|
320
329
|
link: state.link,
|
|
321
330
|
actions: state.actions,
|
|
322
|
-
size: state.
|
|
331
|
+
size: state.identitySize,
|
|
323
332
|
wrap: state.wrap,
|
|
324
333
|
actionsLabel: state.actionsLabel
|
|
325
334
|
};
|
|
326
335
|
}
|
|
327
336
|
|
|
337
|
+
/**
|
|
338
|
+
* What a MIME type is called and drawn as. Labels are what a reader recognises — a format's own
|
|
339
|
+
* acronym where it has a well-known one, the family otherwise — and icons are Font Awesome names
|
|
340
|
+
* in order of preference, used only when the active icon set can draw them.
|
|
341
|
+
* @type {Readonly<Record<string, {label: string, icons: string[]}>>}
|
|
342
|
+
*/
|
|
343
|
+
const FILE_KINDS = Object.freeze({
|
|
344
|
+
pdf: { label: 'PDF', icons: ['file-pdf'] },
|
|
345
|
+
csv: { label: 'CSV', icons: ['file-csv', 'file-excel'] },
|
|
346
|
+
tsv: { label: 'TSV', icons: ['file-csv', 'file-excel'] },
|
|
347
|
+
spreadsheet: { label: 'Spreadsheet', icons: ['file-excel'] },
|
|
348
|
+
document: { label: 'Document', icons: ['file-word', 'file-lines'] },
|
|
349
|
+
presentation: { label: 'Presentation', icons: ['file-powerpoint'] },
|
|
350
|
+
text: { label: 'Text', icons: ['file-lines'] },
|
|
351
|
+
markdown: { label: 'Markdown', icons: ['file-lines'] },
|
|
352
|
+
html: { label: 'HTML', icons: ['file-code', 'code'] },
|
|
353
|
+
css: { label: 'CSS', icons: ['file-code', 'code'] },
|
|
354
|
+
javascript: { label: 'JavaScript', icons: ['file-code', 'code'] },
|
|
355
|
+
json: { label: 'JSON', icons: ['file-code', 'code'] },
|
|
356
|
+
xml: { label: 'XML', icons: ['file-code', 'code'] },
|
|
357
|
+
zip: { label: 'ZIP', icons: ['file-zipper'] },
|
|
358
|
+
gzip: { label: 'GZIP', icons: ['file-zipper'] },
|
|
359
|
+
tar: { label: 'TAR', icons: ['file-zipper'] },
|
|
360
|
+
rar: { label: 'RAR', icons: ['file-zipper'] },
|
|
361
|
+
sevenZip: { label: '7Z', icons: ['file-zipper'] },
|
|
362
|
+
email: { label: 'Email', icons: ['envelope'] },
|
|
363
|
+
calendar: { label: 'Calendar', icons: ['calendar'] },
|
|
364
|
+
contact: { label: 'Contact', icons: ['address-card'] },
|
|
365
|
+
image: { label: 'Image', icons: ['file-image', 'image'] },
|
|
366
|
+
video: { label: 'Video', icons: ['file-video', 'video'] },
|
|
367
|
+
audio: { label: 'Audio', icons: ['file-audio'] },
|
|
368
|
+
font: { label: 'Font', icons: ['font'] }
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
/** Exact MIME types, lower case and without parameters, mapped to their kind. */
|
|
372
|
+
const MIME_KINDS = new Map(Object.entries({
|
|
373
|
+
'application/pdf': 'pdf',
|
|
374
|
+
'text/csv': 'csv',
|
|
375
|
+
'application/csv': 'csv',
|
|
376
|
+
'text/comma-separated-values': 'csv',
|
|
377
|
+
'text/tab-separated-values': 'tsv',
|
|
378
|
+
'application/vnd.ms-excel': 'spreadsheet',
|
|
379
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'spreadsheet',
|
|
380
|
+
'application/vnd.oasis.opendocument.spreadsheet': 'spreadsheet',
|
|
381
|
+
'application/msword': 'document',
|
|
382
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'document',
|
|
383
|
+
'application/vnd.oasis.opendocument.text': 'document',
|
|
384
|
+
'application/rtf': 'document',
|
|
385
|
+
'text/rtf': 'document',
|
|
386
|
+
'application/vnd.ms-powerpoint': 'presentation',
|
|
387
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'presentation',
|
|
388
|
+
'application/vnd.oasis.opendocument.presentation': 'presentation',
|
|
389
|
+
'text/plain': 'text',
|
|
390
|
+
'text/markdown': 'markdown',
|
|
391
|
+
'text/x-markdown': 'markdown',
|
|
392
|
+
'text/html': 'html',
|
|
393
|
+
'application/xhtml+xml': 'html',
|
|
394
|
+
'text/css': 'css',
|
|
395
|
+
'text/javascript': 'javascript',
|
|
396
|
+
'application/javascript': 'javascript',
|
|
397
|
+
'application/json': 'json',
|
|
398
|
+
'application/ld+json': 'json',
|
|
399
|
+
'application/xml': 'xml',
|
|
400
|
+
'text/xml': 'xml',
|
|
401
|
+
'application/zip': 'zip',
|
|
402
|
+
'application/x-zip-compressed': 'zip',
|
|
403
|
+
'application/gzip': 'gzip',
|
|
404
|
+
'application/x-gzip': 'gzip',
|
|
405
|
+
'application/x-tar': 'tar',
|
|
406
|
+
'application/vnd.rar': 'rar',
|
|
407
|
+
'application/x-rar-compressed': 'rar',
|
|
408
|
+
'application/x-7z-compressed': 'sevenZip',
|
|
409
|
+
'message/rfc822': 'email',
|
|
410
|
+
'text/calendar': 'calendar',
|
|
411
|
+
'text/vcard': 'contact',
|
|
412
|
+
'text/x-vcard': 'contact'
|
|
413
|
+
}));
|
|
414
|
+
|
|
415
|
+
/** Top-level MIME types that name a whole family. */
|
|
416
|
+
const MIME_FAMILIES = new Map([
|
|
417
|
+
['image', 'image'], ['video', 'video'], ['audio', 'audio'], ['font', 'font'], ['text', 'text']
|
|
418
|
+
]);
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* @param {string} mime MIME type, possibly with parameters.
|
|
422
|
+
* @returns {{label: string, icons: string[]}|null}
|
|
423
|
+
*/
|
|
424
|
+
function fileKind(mime) {
|
|
425
|
+
const essence = String(mime ?? '').split(';')[0].trim().toLowerCase();
|
|
426
|
+
if (!essence) return null;
|
|
427
|
+
const kind = MIME_KINDS.get(essence) ?? MIME_FAMILIES.get(essence.split('/')[0]);
|
|
428
|
+
return kind ? FILE_KINDS[kind] : null;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* A readable name for a file's type: "PDF", "CSV", "Image", "Spreadsheet". An unfamiliar MIME type
|
|
433
|
+
* falls back to the name's extension in capitals ("EXE"); without a MIME type, or with neither,
|
|
434
|
+
* the result is empty — a raw MIME string is never shown.
|
|
435
|
+
* @param {string} mime MIME type, possibly with parameters (`text/csv; charset=utf-8`).
|
|
436
|
+
* @param {string} [name=''] File name, consulted only when the MIME type is unfamiliar.
|
|
437
|
+
* @returns {string}
|
|
438
|
+
*/
|
|
439
|
+
export function fileTypeLabel(mime, name = '') {
|
|
440
|
+
if (!String(mime ?? '').trim()) return '';
|
|
441
|
+
const kind = fileKind(mime);
|
|
442
|
+
if (kind) return kind.label;
|
|
443
|
+
const extension = /\.([a-z0-9]{1,5})$/i.exec(String(name ?? ''))?.[1];
|
|
444
|
+
return extension ? extension.toUpperCase() : '';
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* The icon name for a MIME type: a type-specific glyph when the active icon set can draw one,
|
|
449
|
+
* `'file'` otherwise. Font Awesome resolves every name it is given; the bundled inline set only
|
|
450
|
+
* those it carries or that `registerIcons()` added, so a missing glyph never shows a placeholder.
|
|
451
|
+
* @param {string} mime MIME type, possibly with parameters.
|
|
452
|
+
* @returns {string}
|
|
453
|
+
*/
|
|
454
|
+
export function fileTypeIcon(mime) {
|
|
455
|
+
const candidates = fileKind(mime)?.icons ?? [];
|
|
456
|
+
if (!candidates.length) return 'file';
|
|
457
|
+
if (getIconConfig().provider === 'fontawesome') return candidates[0];
|
|
458
|
+
const available = new Set(iconNames());
|
|
459
|
+
return candidates.find((name) => available.has(name)) ?? 'file';
|
|
460
|
+
}
|
|
461
|
+
|
|
328
462
|
/** @param {FileItemState} state @returns {string} */
|
|
329
463
|
function fileStatusLabel(state) {
|
|
330
464
|
return state.statusLabel === null ? STATUS_LABELS[state.status] : state.statusLabel;
|
|
@@ -295,7 +295,7 @@ export class Filter extends Component {
|
|
|
295
295
|
actionButton('add', this._value.root.id, 'plus', this._message('filter.addFilter', 'Add filter'), { ref: 'add', disabled: this._blocked() }),
|
|
296
296
|
this.options.allowGroups ? actionButton('add-group', this._value.root.id, 'folder', this._message('filter.addGroup', 'Add group'), { disabled: this._blocked() }) : null,
|
|
297
297
|
actionButton('clear', '', 'x', String(this.options.clearLabel ?? this._message('filter.clear', 'Clear')), { disabled: this._blocked() }),
|
|
298
|
-
this.options.showApply ? actionButton('apply', '', 'check', String(this.options.applyLabel ?? this._message('filter.apply', 'Apply filters')), {
|
|
298
|
+
this.options.showApply ? actionButton('apply', '', 'check', String(this.options.applyLabel ?? this._message('filter.apply', 'Apply filters')), { kind: 'primary', disabled: this._disabled }) : null
|
|
299
299
|
);
|
|
300
300
|
this.el.replaceChildren(status, tree, actions);
|
|
301
301
|
this.refs.status = status;
|
|
@@ -631,9 +631,13 @@ export class Filter extends Component {
|
|
|
631
631
|
}
|
|
632
632
|
}
|
|
633
633
|
|
|
634
|
-
/** @param {string} action @param {string} id @param {string} iconName @param {string} label @param {Record<string,any>} [props={}] @returns {HTMLElement} */
|
|
634
|
+
/** @param {string} action @param {string} id @param {string} iconName @param {string} label @param {Record<string,any>} [props={}] `kind` picks the Button kind; the rest are element properties. @returns {HTMLElement} */
|
|
635
635
|
function actionButton(action, id, iconName, label, props = {}) {
|
|
636
|
-
|
|
636
|
+
const { kind, ...rest } = props;
|
|
637
|
+
return h('button', {
|
|
638
|
+
type: 'button', class: 'zx-btn', ...rest,
|
|
639
|
+
dataset: { filterAction: action, nodeId: id, size: 'sm', ...(kind ? { kind } : {}) }
|
|
640
|
+
}, icon(iconName), h('span', {}, label));
|
|
637
641
|
}
|
|
638
642
|
|
|
639
643
|
/** @param {FilterField} field @param {any} node @param {any} value @param {boolean} multiple @param {boolean} disabled @param {number} [index=0] @param {FilterValueMessages} [messages=BUILT_IN_VALUE_MESSAGES] @returns {HTMLSelectElement|null} */
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
flex: none;
|
|
18
18
|
min-inline-size: 0;
|
|
19
19
|
flex-direction: column;
|
|
20
|
-
border-inline-end: 1px solid var(--zx-color-border);
|
|
20
|
+
border-inline-end: 1px solid var(--zx-color-border-subtle);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
.zx-finder__list {
|
|
@@ -32,9 +32,12 @@
|
|
|
32
32
|
display: flex;
|
|
33
33
|
align-items: center;
|
|
34
34
|
gap: var(--zx-space-2);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
/* A browsing row: a space-1 margin round one line of text, and the smallest radius, so a
|
|
36
|
+
column of them reads as a list rather than a stack of buttons. */
|
|
37
|
+
min-block-size: calc(var(--zx-control-height) - var(--zx-space-1));
|
|
38
|
+
padding: var(--zx-space-1) var(--zx-space-2);
|
|
39
|
+
border-radius: var(--zx-radius-sm);
|
|
40
|
+
line-height: 1.25;
|
|
38
41
|
cursor: pointer;
|
|
39
42
|
}
|
|
40
43
|
|
|
@@ -42,10 +45,12 @@
|
|
|
42
45
|
background: var(--zx-color-bg-hover);
|
|
43
46
|
}
|
|
44
47
|
|
|
48
|
+
/* The selected row of each column is the path you are on, not a pick: the neutral current fill,
|
|
49
|
+
the full text colour and a heavier label. */
|
|
45
50
|
.zx-finder__row[aria-selected="true"] {
|
|
46
|
-
background: var(--zx-color-bg-
|
|
47
|
-
color: var(--zx-color-
|
|
48
|
-
font-weight:
|
|
51
|
+
background: var(--zx-color-bg-current);
|
|
52
|
+
color: var(--zx-color-text);
|
|
53
|
+
font-weight: var(--zx-weight-semibold);
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
.zx-finder__row:focus-visible {
|
|
@@ -53,19 +58,16 @@
|
|
|
53
58
|
box-shadow: var(--zx-focus-ring);
|
|
54
59
|
}
|
|
55
60
|
|
|
61
|
+
/* Icons are quieter than labels, folders included; the current row's icon takes the text colour. */
|
|
56
62
|
.zx-finder__icon {
|
|
57
63
|
display: inline-grid;
|
|
58
64
|
place-items: center;
|
|
59
65
|
flex: none;
|
|
60
|
-
color: var(--zx-color-text-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
.zx-finder__row[data-branch] .zx-finder__icon {
|
|
64
|
-
color: var(--zx-color-warning);
|
|
66
|
+
color: var(--zx-color-text-subtle);
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
.zx-finder__row[aria-selected="true"] .zx-finder__icon {
|
|
68
|
-
color:
|
|
70
|
+
color: var(--zx-color-text);
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
.zx-finder__label {
|
|
@@ -29,11 +29,12 @@
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
.zx-form__actions .zx-btn[type="submit"] {
|
|
32
|
-
border-color: var(--zx-color-accent);
|
|
33
|
-
background: var(--zx-color-accent);
|
|
34
|
-
color: var(--zx-color-on-accent);
|
|
32
|
+
border-color: var(--zx-color-accent-solid);
|
|
33
|
+
background: var(--zx-color-accent-solid);
|
|
34
|
+
color: var(--zx-color-on-accent-solid);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
.zx-form__actions .zx-btn[type="submit"]:hover {
|
|
38
|
-
|
|
38
|
+
border-color: var(--zx-color-accent-solid-hover);
|
|
39
|
+
background: var(--zx-color-accent-solid-hover);
|
|
39
40
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Component } from '../../core/component.js';
|
|
2
2
|
import { h } from '../../core/dom.js';
|
|
3
3
|
import { icon as createIcon } from '../../core/icons.js';
|
|
4
|
+
import { uid } from '../../core/util.js';
|
|
4
5
|
import { Fieldset } from '../fieldset/fieldset.js';
|
|
5
6
|
import { coerceFloat, coerceInteger } from '../field/field.js';
|
|
6
7
|
|
|
@@ -22,6 +23,9 @@ import { coerceFloat, coerceInteger } from '../field/field.js';
|
|
|
22
23
|
* @property {Array<Fieldset|import('../fieldset/fieldset.js').FieldsetOptions>} [fieldsets=[]] Initial fieldsets.
|
|
23
24
|
* @property {FormAction[]} [actions=[]] Form action descriptors.
|
|
24
25
|
* @property {boolean} [novalidate=true] Native `novalidate` state.
|
|
26
|
+
* @property {string|null} [idPrefix=null] Prefix of the DOM ids generated for the fields of the
|
|
27
|
+
* fieldsets this form builds, as `<idPrefix>-<key>`. Null generates one per form instance, so a
|
|
28
|
+
* form mounted twice never repeats an id. A fieldset's own `idPrefix` wins.
|
|
25
29
|
* @property {(event: CustomEvent<{values: Record<string, unknown>}>) => void} [onsubmit] Submit listener.
|
|
26
30
|
* @property {(event: CustomEvent<{errors: Record<string, string>}>) => void} [oninvalid] Invalid listener.
|
|
27
31
|
* @property {(event: CustomEvent<{id: string, value: unknown}>) => void} [onchange] Field change listener.
|
|
@@ -42,6 +46,7 @@ export class Form extends Component {
|
|
|
42
46
|
fieldsets: [],
|
|
43
47
|
actions: [],
|
|
44
48
|
novalidate: true,
|
|
49
|
+
idPrefix: null,
|
|
45
50
|
msg: {
|
|
46
51
|
'form.required': 'This field is required.',
|
|
47
52
|
'form.int': 'Enter a whole number.',
|
|
@@ -56,6 +61,7 @@ export class Form extends Component {
|
|
|
56
61
|
this._ownedFieldsets = new Set();
|
|
57
62
|
this._origins = new Map();
|
|
58
63
|
this._ownedNodes = [];
|
|
64
|
+
this._idPrefix = this.options.idPrefix ? String(this.options.idPrefix) : uid('zx-form');
|
|
59
65
|
this._novalidateAttribute = root.getAttribute('novalidate');
|
|
60
66
|
root.toggleAttribute('novalidate', Boolean(this.options.novalidate));
|
|
61
67
|
|
|
@@ -81,13 +87,16 @@ export class Form extends Component {
|
|
|
81
87
|
}
|
|
82
88
|
|
|
83
89
|
/**
|
|
84
|
-
* Adds an existing fieldset or constructs one from options.
|
|
90
|
+
* Adds an existing fieldset or constructs one from options. A constructed fieldset generates its
|
|
91
|
+
* field ids from the form's `idPrefix` unless its options carry their own.
|
|
85
92
|
* @param {Fieldset|import('../fieldset/fieldset.js').FieldsetOptions} fieldset Fieldset source.
|
|
86
93
|
* @returns {Fieldset}
|
|
87
94
|
*/
|
|
88
95
|
addFieldset(fieldset) {
|
|
89
96
|
const owned = !(fieldset instanceof Fieldset);
|
|
90
|
-
const instance = owned
|
|
97
|
+
const instance = owned
|
|
98
|
+
? new Fieldset(null, { ...(fieldset ?? {}), idPrefix: fieldset?.idPrefix ?? this._idPrefix })
|
|
99
|
+
: fieldset;
|
|
91
100
|
if (this._fieldsets.includes(instance)) return instance;
|
|
92
101
|
if (owned) this._ownedFieldsets.add(instance);
|
|
93
102
|
else this._origins.set(instance, { parent: instance.el.parentNode, next: instance.el.nextSibling });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import { h, safeHref } from '../../core/dom.js';
|
|
3
3
|
import { icon } from '../../core/icons.js';
|
|
4
|
-
import { avatarInitials } from '../avatar/avatar.js';
|
|
4
|
+
import { AVATAR_TONES, avatarInitials, avatarTone } from '../avatar/avatar.js';
|
|
5
5
|
import { badge } from '../badge/badge.js';
|
|
6
6
|
|
|
7
7
|
/** @typedef {Record<string, any>} KanbanRecord */
|
|
@@ -28,6 +28,9 @@ import { badge } from '../badge/badge.js';
|
|
|
28
28
|
* @property {string} name Person's name, used as the accessible label.
|
|
29
29
|
* @property {string|null} src Safe image URL, or null for the initials fallback.
|
|
30
30
|
* @property {string} initials Fallback initials.
|
|
31
|
+
* @property {import('../avatar/avatar.js').AvatarTone} tone Fallback tint: a descriptor's own
|
|
32
|
+
* `tone` when it names one of `AVATAR_TONES`, otherwise `avatarTone(name)` — the colour the same
|
|
33
|
+
* person has in every Avatar.
|
|
31
34
|
*/
|
|
32
35
|
|
|
33
36
|
/** Semantic intents an indicator may carry, in the order they are documented. */
|
|
@@ -132,6 +135,8 @@ export function createKanbanProgress(progress, label, valueText) {
|
|
|
132
135
|
/**
|
|
133
136
|
* Normalizes one or more responsible people. Names, name lists, and descriptor objects all
|
|
134
137
|
* resolve to the same shape, and an unsafe image URL degrades to initials rather than failing.
|
|
138
|
+
* A descriptor may name its `tone`; otherwise the tone is derived from the name exactly as Avatar
|
|
139
|
+
* derives it.
|
|
135
140
|
* @param {unknown} value Raw field value.
|
|
136
141
|
* @returns {KanbanAssignee[]} Normalized people, in the order supplied.
|
|
137
142
|
*/
|
|
@@ -148,7 +153,8 @@ export function resolveKanbanAssignees(value) {
|
|
|
148
153
|
if (!name) return null;
|
|
149
154
|
const src = safeHref(source.src ?? source.image ?? null);
|
|
150
155
|
const initials = source.initials == null ? avatarInitials(name) : String(source.initials);
|
|
151
|
-
|
|
156
|
+
const tone = AVATAR_TONES.includes(source.tone) ? source.tone : avatarTone(name);
|
|
157
|
+
return { name, src, initials, tone };
|
|
152
158
|
}).filter(Boolean));
|
|
153
159
|
}
|
|
154
160
|
|
|
@@ -185,8 +191,10 @@ export function createKanbanAvatars(people, max, groupLabel, overflowLabel) {
|
|
|
185
191
|
const split = kanbanAvatarLimit(max, people.length);
|
|
186
192
|
const shown = people.slice(0, split.shown);
|
|
187
193
|
const hidden = split.hidden;
|
|
194
|
+
// Each face carries its tone, so the initials sit on the tint the person has in every Avatar.
|
|
188
195
|
const faces = shown.map((person) => h('span', {
|
|
189
|
-
class: 'zx-kanban-view__avatar', ariaHidden: 'true', title: person.name || undefined
|
|
196
|
+
class: 'zx-kanban-view__avatar', ariaHidden: 'true', title: person.name || undefined,
|
|
197
|
+
dataset: { tone: person.tone ?? avatarTone(person.name) }
|
|
190
198
|
}, person.src
|
|
191
199
|
? h('img', { class: 'zx-kanban-view__avatar-image', src: person.src, alt: '', draggable: false })
|
|
192
200
|
: h('span', { class: 'zx-kanban-view__avatar-initials' }, person.initials)));
|
|
@@ -120,8 +120,8 @@
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
.zx-kanban-view__move-handle[aria-pressed="true"] {
|
|
123
|
-
background: var(--zx-color-
|
|
124
|
-
color: var(--zx-color-
|
|
123
|
+
background: var(--zx-color-bg-current);
|
|
124
|
+
color: var(--zx-color-text);
|
|
125
125
|
cursor: grabbing;
|
|
126
126
|
}
|
|
127
127
|
|
|
@@ -348,7 +348,7 @@
|
|
|
348
348
|
}
|
|
349
349
|
|
|
350
350
|
.zx-kanban-view .zx-record-card__eyebrow-group[data-card-slot="eyebrow-start"] .zx-record-card__field {
|
|
351
|
-
border-radius: var(--zx-radius-
|
|
351
|
+
border-radius: var(--zx-radius-sm);
|
|
352
352
|
background: var(--zx-color-bg-muted);
|
|
353
353
|
padding: calc(var(--zx-space-1) / 2) var(--zx-space-2);
|
|
354
354
|
}
|
|
@@ -419,6 +419,12 @@
|
|
|
419
419
|
.zx-kanban-view .zx-record-card[data-grabbed="true"] {
|
|
420
420
|
outline: 2px solid var(--zx-color-accent);
|
|
421
421
|
outline-offset: 2px;
|
|
422
|
+
/* A card picked up from the keyboard is the item being dragged, so it lifts like one. */
|
|
423
|
+
box-shadow: var(--zx-drag-shadow);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.zx-kanban-view .zx-record-card[data-grabbed="true"]:focus-visible {
|
|
427
|
+
box-shadow: var(--zx-focus-ring), var(--zx-drag-shadow);
|
|
422
428
|
}
|
|
423
429
|
|
|
424
430
|
.zx-kanban-view__empty {
|
|
@@ -470,10 +476,6 @@
|
|
|
470
476
|
}
|
|
471
477
|
}
|
|
472
478
|
|
|
473
|
-
[data-zx-density="compact"] .zx-kanban-view {
|
|
474
|
-
--zx-kanban-card-padding-inline: var(--zx-space-2);
|
|
475
|
-
}
|
|
476
|
-
|
|
477
479
|
@media (prefers-reduced-motion: no-preference) {
|
|
478
480
|
.zx-kanban-view__collapse,
|
|
479
481
|
.zx-kanban-view__move-handle,
|
|
@@ -651,7 +653,8 @@
|
|
|
651
653
|
border: 1px solid var(--zx-color-border);
|
|
652
654
|
border-radius: var(--zx-radius-md);
|
|
653
655
|
background: var(--zx-color-bg-raised);
|
|
654
|
-
|
|
656
|
+
/* The item in flight: lifted above every resting card by the shared drag shadow. */
|
|
657
|
+
box-shadow: var(--zx-drag-shadow);
|
|
655
658
|
padding: var(--zx-space-1) var(--zx-space-2);
|
|
656
659
|
color: var(--zx-color-text);
|
|
657
660
|
font-size: var(--zx-text-xs);
|
|
@@ -704,7 +707,6 @@
|
|
|
704
707
|
color: var(--zx-color-text-muted);
|
|
705
708
|
font-size: var(--zx-text-xs);
|
|
706
709
|
font-variant-numeric: tabular-nums;
|
|
707
|
-
letter-spacing: .02em;
|
|
708
710
|
text-overflow: ellipsis;
|
|
709
711
|
white-space: nowrap;
|
|
710
712
|
}
|
|
@@ -767,6 +769,11 @@
|
|
|
767
769
|
min-inline-size: 0;
|
|
768
770
|
}
|
|
769
771
|
|
|
772
|
+
/*
|
|
773
|
+
* A face is a small Avatar: the initials on the tint of the tone the component derives from the
|
|
774
|
+
* name with Avatar's own hash (`data-tone`), so a person has the same colour on the board as
|
|
775
|
+
* everywhere else. The surface-coloured edge is what separates overlapping faces.
|
|
776
|
+
*/
|
|
770
777
|
.zx-kanban-view__avatar {
|
|
771
778
|
display: inline-grid;
|
|
772
779
|
flex: none;
|
|
@@ -776,17 +783,27 @@
|
|
|
776
783
|
overflow: hidden;
|
|
777
784
|
border: 1px solid var(--zx-color-bg-surface);
|
|
778
785
|
border-radius: var(--zx-radius-full);
|
|
779
|
-
background: var(--zx-color-bg-muted);
|
|
780
|
-
color: var(--zx-color-text-muted);
|
|
786
|
+
background: var(--zx-kanban-avatar-bg, var(--zx-color-bg-muted));
|
|
787
|
+
color: var(--zx-kanban-avatar-fg, var(--zx-color-text-muted));
|
|
781
788
|
}
|
|
782
789
|
|
|
790
|
+
.zx-kanban-view__avatar[data-tone="blue"] { --zx-kanban-avatar-bg: var(--zx-color-tone-blue-bg); --zx-kanban-avatar-fg: var(--zx-color-tone-blue-fg); }
|
|
791
|
+
.zx-kanban-view__avatar[data-tone="teal"] { --zx-kanban-avatar-bg: var(--zx-color-tone-teal-bg); --zx-kanban-avatar-fg: var(--zx-color-tone-teal-fg); }
|
|
792
|
+
.zx-kanban-view__avatar[data-tone="green"] { --zx-kanban-avatar-bg: var(--zx-color-tone-green-bg); --zx-kanban-avatar-fg: var(--zx-color-tone-green-fg); }
|
|
793
|
+
.zx-kanban-view__avatar[data-tone="amber"] { --zx-kanban-avatar-bg: var(--zx-color-tone-amber-bg); --zx-kanban-avatar-fg: var(--zx-color-tone-amber-fg); }
|
|
794
|
+
.zx-kanban-view__avatar[data-tone="orange"] { --zx-kanban-avatar-bg: var(--zx-color-tone-orange-bg); --zx-kanban-avatar-fg: var(--zx-color-tone-orange-fg); }
|
|
795
|
+
.zx-kanban-view__avatar[data-tone="red"] { --zx-kanban-avatar-bg: var(--zx-color-tone-red-bg); --zx-kanban-avatar-fg: var(--zx-color-tone-red-fg); }
|
|
796
|
+
.zx-kanban-view__avatar[data-tone="pink"] { --zx-kanban-avatar-bg: var(--zx-color-tone-pink-bg); --zx-kanban-avatar-fg: var(--zx-color-tone-pink-fg); }
|
|
797
|
+
.zx-kanban-view__avatar[data-tone="violet"] { --zx-kanban-avatar-bg: var(--zx-color-tone-violet-bg); --zx-kanban-avatar-fg: var(--zx-color-tone-violet-fg); }
|
|
798
|
+
.zx-kanban-view__avatar[data-tone="slate"] { --zx-kanban-avatar-bg: var(--zx-color-tone-slate-bg); --zx-kanban-avatar-fg: var(--zx-color-tone-slate-fg); }
|
|
799
|
+
|
|
783
800
|
.zx-kanban-view__avatar + .zx-kanban-view__avatar {
|
|
784
801
|
margin-inline-start: calc(var(--zx-space-2) * -1);
|
|
785
802
|
}
|
|
786
803
|
|
|
787
804
|
.zx-kanban-view__avatar[data-overflow="true"] {
|
|
788
|
-
background: var(--zx-color-
|
|
789
|
-
color: var(--zx-color-
|
|
805
|
+
background: var(--zx-color-bg-current);
|
|
806
|
+
color: var(--zx-color-text-muted);
|
|
790
807
|
}
|
|
791
808
|
|
|
792
809
|
.zx-kanban-view__avatar-image {
|
|
@@ -795,13 +812,20 @@
|
|
|
795
812
|
object-fit: cover;
|
|
796
813
|
}
|
|
797
814
|
|
|
815
|
+
/* Avatar's initials at the small size: 0.4 of the face, never under 10px, semibold. */
|
|
798
816
|
.zx-kanban-view__avatar-initials {
|
|
799
|
-
font-size:
|
|
800
|
-
font-weight:
|
|
817
|
+
font-size: max(10px, calc(1.5rem * .4));
|
|
818
|
+
font-weight: var(--zx-weight-semibold);
|
|
819
|
+
letter-spacing: 0;
|
|
801
820
|
line-height: 1;
|
|
802
821
|
text-transform: uppercase;
|
|
803
822
|
}
|
|
804
823
|
|
|
824
|
+
/* The overflow chip is a count, not a person: it keeps the text size it can be read at. */
|
|
825
|
+
.zx-kanban-view__avatar[data-overflow="true"] .zx-kanban-view__avatar-initials {
|
|
826
|
+
font-size: var(--zx-text-xs);
|
|
827
|
+
}
|
|
828
|
+
|
|
805
829
|
/* ── Move handle ──────────────────────────────────────────────────────────────────────────── */
|
|
806
830
|
|
|
807
831
|
/* The card is the pointer drag source, so the handle is keyboard furniture: announced and in tab
|
|
@@ -1742,12 +1742,12 @@ export class KanbanView extends RecordView {
|
|
|
1742
1742
|
/** @returns {HTMLElement} Built-in history controls. */
|
|
1743
1743
|
_createHistoryControls() {
|
|
1744
1744
|
this._undoButton = /** @type {HTMLButtonElement} */ (h('button', {
|
|
1745
|
-
class: 'zx-
|
|
1746
|
-
dataset: { kanbanHistory: 'undo' }
|
|
1745
|
+
class: 'zx-btn', type: 'button', disabled: true,
|
|
1746
|
+
dataset: { kanbanHistory: 'undo', size: 'sm', kind: 'ghost' }
|
|
1747
1747
|
}, this._text('undo')));
|
|
1748
1748
|
this._redoButton = /** @type {HTMLButtonElement} */ (h('button', {
|
|
1749
|
-
class: 'zx-
|
|
1750
|
-
dataset: { kanbanHistory: 'redo' }
|
|
1749
|
+
class: 'zx-btn', type: 'button', disabled: true,
|
|
1750
|
+
dataset: { kanbanHistory: 'redo', size: 'sm', kind: 'ghost' }
|
|
1751
1751
|
}, this._text('redo')));
|
|
1752
1752
|
const group = h('div', { class: 'zx-kanban-view__history', role: 'group' },
|
|
1753
1753
|
this._undoButton, this._redoButton);
|