@wordpress/dataviews 4.11.0 → 4.12.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/README.md +1 -1
  3. package/build/components/dataviews-selection-checkbox/index.js +6 -10
  4. package/build/components/dataviews-selection-checkbox/index.js.map +1 -1
  5. package/build/components/dataviews-view-config/index.js +115 -13
  6. package/build/components/dataviews-view-config/index.js.map +1 -1
  7. package/build/dataviews-layouts/table/index.js +2 -2
  8. package/build/dataviews-layouts/table/index.js.map +1 -1
  9. package/build/field-types/integer.js +1 -1
  10. package/build/field-types/integer.js.map +1 -1
  11. package/build/types.js.map +1 -1
  12. package/build-module/components/dataviews-selection-checkbox/index.js +6 -10
  13. package/build-module/components/dataviews-selection-checkbox/index.js.map +1 -1
  14. package/build-module/components/dataviews-view-config/index.js +118 -15
  15. package/build-module/components/dataviews-view-config/index.js.map +1 -1
  16. package/build-module/dataviews-layouts/table/index.js +2 -2
  17. package/build-module/dataviews-layouts/table/index.js.map +1 -1
  18. package/build-module/field-types/integer.js +1 -1
  19. package/build-module/field-types/integer.js.map +1 -1
  20. package/build-module/types.js.map +1 -1
  21. package/build-style/style-rtl.css +25 -22
  22. package/build-style/style.css +25 -22
  23. package/build-types/components/dataviews-selection-checkbox/index.d.ts.map +1 -1
  24. package/build-types/components/dataviews-view-config/index.d.ts.map +1 -1
  25. package/build-types/types.d.ts +1 -1
  26. package/build-types/types.d.ts.map +1 -1
  27. package/build-wp/index.js +338 -181
  28. package/package.json +11 -11
  29. package/src/components/dataviews/style.scss +8 -4
  30. package/src/components/dataviews-footer/style.scss +5 -2
  31. package/src/components/dataviews-selection-checkbox/index.tsx +6 -15
  32. package/src/components/dataviews-view-config/index.tsx +176 -36
  33. package/src/components/dataviews-view-config/style.scss +15 -2
  34. package/src/dataviews-layouts/grid/style.scss +3 -2
  35. package/src/dataviews-layouts/table/index.tsx +2 -2
  36. package/src/field-types/integer.tsx +1 -1
  37. package/src/types.ts +1 -1
  38. package/tsconfig.tsbuildinfo +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/dataviews",
3
- "version": "4.11.0",
3
+ "version": "4.12.0",
4
4
  "description": "DataViews is a component that provides an API to render datasets using different types of layouts (table, grid, list, etc.).",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -46,15 +46,15 @@
46
46
  "dependencies": {
47
47
  "@ariakit/react": "^0.4.15",
48
48
  "@babel/runtime": "7.25.7",
49
- "@wordpress/components": "^29.1.0",
50
- "@wordpress/compose": "^7.15.0",
51
- "@wordpress/data": "^10.15.0",
52
- "@wordpress/element": "^6.15.0",
53
- "@wordpress/i18n": "^5.15.0",
54
- "@wordpress/icons": "^10.15.0",
55
- "@wordpress/primitives": "^4.15.0",
56
- "@wordpress/private-apis": "^1.15.0",
57
- "@wordpress/warning": "^3.15.0",
49
+ "@wordpress/components": "^29.2.0",
50
+ "@wordpress/compose": "^7.16.0",
51
+ "@wordpress/data": "^10.16.0",
52
+ "@wordpress/element": "^6.16.0",
53
+ "@wordpress/i18n": "^5.16.0",
54
+ "@wordpress/icons": "^10.16.0",
55
+ "@wordpress/primitives": "^4.16.0",
56
+ "@wordpress/private-apis": "^1.16.0",
57
+ "@wordpress/warning": "^3.16.0",
58
58
  "clsx": "^2.1.1",
59
59
  "remove-accents": "^0.5.0"
60
60
  },
@@ -67,5 +67,5 @@
67
67
  "scripts": {
68
68
  "build:wp": "node build"
69
69
  },
70
- "gitHead": "75a65eb8ffc168a92042544052f46d080a71ea45"
70
+ "gitHead": "f48b9f56629e400891abb5ae491504de475237ff"
71
71
  }
@@ -18,8 +18,10 @@
18
18
  flex-shrink: 0;
19
19
  position: sticky;
20
20
  left: 0;
21
- transition: padding ease-out 0.1s;
22
- @include reduce-motion( "transition" );
21
+
22
+ @media not (prefers-reduced-motion) {
23
+ transition: padding ease-out 0.1s;
24
+ }
23
25
  }
24
26
 
25
27
  .dataviews-no-results,
@@ -29,8 +31,10 @@
29
31
  display: flex;
30
32
  align-items: center;
31
33
  justify-content: center;
32
- transition: padding ease-out 0.1s;
33
- @include reduce-motion( "transition" );
34
+
35
+ @media not (prefers-reduced-motion) {
36
+ transition: padding ease-out 0.1s;
37
+ }
34
38
  }
35
39
 
36
40
  @container (max-width: 430px) {
@@ -6,8 +6,11 @@
6
6
  padding: $grid-unit-15 $grid-unit-60;
7
7
  border-top: $border-width solid $gray-100;
8
8
  flex-shrink: 0;
9
- transition: padding ease-out 0.1s;
10
- @include reduce-motion("transition");
9
+
10
+ @media not (prefers-reduced-motion) {
11
+ transition: padding ease-out 0.1s;
12
+ }
13
+
11
14
  z-index: z-index(".dataviews-footer");
12
15
  }
13
16
 
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
- import { __, sprintf } from '@wordpress/i18n';
5
4
  import { CheckboxControl } from '@wordpress/components';
5
+ import { __ } from '@wordpress/i18n';
6
6
 
7
7
  /**
8
8
  * Internal dependencies
@@ -29,20 +29,11 @@ export default function DataViewsSelectionCheckbox< Item >( {
29
29
  }: DataViewsSelectionCheckboxProps< Item > ) {
30
30
  const id = getItemId( item );
31
31
  const checked = ! disabled && selection.includes( id );
32
- let selectionLabel;
33
- if ( titleField?.getValue && item ) {
34
- // eslint-disable-next-line @wordpress/valid-sprintf
35
- selectionLabel = sprintf(
36
- checked
37
- ? /* translators: %s: item title. */ __( 'Deselect item: %s' )
38
- : /* translators: %s: item title. */ __( 'Select item: %s' ),
39
- titleField.getValue( { item } )
40
- );
41
- } else {
42
- selectionLabel = checked
43
- ? __( 'Select a new item' )
44
- : __( 'Deselect item' );
45
- }
32
+
33
+ // Fallback label to ensure accessibility
34
+ const selectionLabel =
35
+ titleField?.getValue?.( { item } ) || __( '(no title)' );
36
+
46
37
  return (
47
38
  <CheckboxControl
48
39
  className="dataviews-selection-checkbox"
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import type { ChangeEvent } from 'react';
4
+ import type { ChangeEvent, ReactNode } from 'react';
5
+ import clsx from 'clsx';
5
6
 
6
7
  /**
7
8
  * WordPress dependencies
@@ -26,7 +27,7 @@ import {
26
27
  Icon,
27
28
  } from '@wordpress/components';
28
29
  import { __, _x, sprintf } from '@wordpress/i18n';
29
- import { memo, useContext, useMemo } from '@wordpress/element';
30
+ import { memo, useContext, useMemo, useState } from '@wordpress/element';
30
31
  import {
31
32
  chevronDown,
32
33
  chevronUp,
@@ -34,6 +35,7 @@ import {
34
35
  seen,
35
36
  unseen,
36
37
  lock,
38
+ moreVertical,
37
39
  } from '@wordpress/icons';
38
40
  import warning from '@wordpress/warning';
39
41
  import { useInstanceId } from '@wordpress/compose';
@@ -253,8 +255,66 @@ function ItemsPerPageControl() {
253
255
  );
254
256
  }
255
257
 
258
+ function PreviewOptions( {
259
+ previewOptions,
260
+ onChangePreviewOption,
261
+ onMenuOpenChange,
262
+ activeOption,
263
+ }: {
264
+ previewOptions?: Array< { label: string; id: string } >;
265
+ onChangePreviewOption?: ( newPreviewOption: string ) => void;
266
+ onMenuOpenChange: ( isOpen: boolean ) => void;
267
+ activeOption?: string;
268
+ } ) {
269
+ const focusPreviewOptionsField = ( id: string ) => {
270
+ // Focus the visibility button to avoid focus loss.
271
+ // Our code is safe against the component being unmounted, so we don't need to worry about cleaning the timeout.
272
+ // eslint-disable-next-line @wordpress/react-no-unsafe-timeout
273
+ setTimeout( () => {
274
+ const element = document.querySelector(
275
+ `.dataviews-field-control__field-${ id } .dataviews-field-control__field-preview-options-button`
276
+ );
277
+ if ( element instanceof HTMLElement ) {
278
+ element.focus();
279
+ }
280
+ }, 50 );
281
+ };
282
+ return (
283
+ <Menu onOpenChange={ onMenuOpenChange }>
284
+ <Menu.TriggerButton
285
+ render={
286
+ <Button
287
+ className="dataviews-field-control__field-preview-options-button"
288
+ size="compact"
289
+ icon={ moreVertical }
290
+ label={ __( 'Preview' ) }
291
+ />
292
+ }
293
+ />
294
+ <Menu.Popover>
295
+ { previewOptions?.map( ( { id, label } ) => {
296
+ return (
297
+ <Menu.RadioItem
298
+ key={ id }
299
+ value={ id }
300
+ checked={ id === activeOption }
301
+ onChange={ () => {
302
+ onChangePreviewOption?.( id );
303
+ focusPreviewOptionsField( id );
304
+ } }
305
+ >
306
+ <Menu.ItemLabel>{ label }</Menu.ItemLabel>
307
+ </Menu.RadioItem>
308
+ );
309
+ } ) }
310
+ </Menu.Popover>
311
+ </Menu>
312
+ );
313
+ }
256
314
  function FieldItem( {
257
315
  field,
316
+ label,
317
+ description,
258
318
  isVisible,
259
319
  isFirst,
260
320
  isLast,
@@ -262,8 +322,12 @@ function FieldItem( {
262
322
  onToggleVisibility,
263
323
  onMoveUp,
264
324
  onMoveDown,
325
+ previewOptions,
326
+ onChangePreviewOption,
265
327
  }: {
266
328
  field: NormalizedField< any >;
329
+ label?: string;
330
+ description?: string;
267
331
  isVisible: boolean;
268
332
  isFirst?: boolean;
269
333
  isLast?: boolean;
@@ -271,7 +335,12 @@ function FieldItem( {
271
335
  onToggleVisibility?: () => void;
272
336
  onMoveUp?: () => void;
273
337
  onMoveDown?: () => void;
338
+ previewOptions?: Array< { label: string; id: string } >;
339
+ onChangePreviewOption?: ( newPreviewOption: string ) => void;
274
340
  } ) {
341
+ const [ isChangingPreviewOption, setIsChangingPreviewOption ] =
342
+ useState< boolean >( false );
343
+
275
344
  const focusVisibilityField = () => {
276
345
  // Focus the visibility button to avoid focus loss.
277
346
  // Our code is safe against the component being unmounted, so we don't need to worry about cleaning the timeout.
@@ -290,7 +359,17 @@ function FieldItem( {
290
359
  <Item>
291
360
  <HStack
292
361
  expanded
293
- className={ `dataviews-field-control__field dataviews-field-control__field-${ field.id }` }
362
+ className={ clsx(
363
+ 'dataviews-field-control__field',
364
+ `dataviews-field-control__field-${ field.id }`,
365
+ // The actions are hidden when the mouse is not hovering the item, or focus
366
+ // is outside the item.
367
+ // For actions that require a popover, a menu etc, that would mean that when the interactive element
368
+ // opens and the focus goes there the actions would be hidden.
369
+ // To avoid that we add a class to the item, that makes sure actions are visible while there is some
370
+ // interaction with the item.
371
+ { 'is-interacting': isChangingPreviewOption }
372
+ ) }
294
373
  justify="flex-start"
295
374
  >
296
375
  <span className="dataviews-field-control__icon">
@@ -298,8 +377,15 @@ function FieldItem( {
298
377
  <Icon icon={ lock } />
299
378
  ) }
300
379
  </span>
301
- <span className="dataviews-field-control__label">
302
- { field.label }
380
+ <span className="dataviews-field-control__label-sub-label-container">
381
+ <span className="dataviews-field-control__label">
382
+ { label || field.label }
383
+ </span>
384
+ { description && (
385
+ <span className="dataviews-field-control__sub-label">
386
+ { description }
387
+ </span>
388
+ ) }
303
389
  </span>
304
390
  <HStack
305
391
  justify="flex-end"
@@ -368,6 +454,14 @@ function FieldItem( {
368
454
  }
369
455
  />
370
456
  ) }
457
+ { previewOptions && (
458
+ <PreviewOptions
459
+ previewOptions={ previewOptions }
460
+ onChangePreviewOption={ onChangePreviewOption }
461
+ onMenuOpenChange={ setIsChangingPreviewOption }
462
+ activeOption={ field.id }
463
+ />
464
+ ) }
371
465
  </HStack>
372
466
  </HStack>
373
467
  </Item>
@@ -461,7 +555,8 @@ function FieldControl() {
461
555
  const hiddenFields = fields.filter(
462
556
  ( f ) =>
463
557
  ! visibleFieldIds.includes( f.id ) &&
464
- ! togglableFields.includes( f.id )
558
+ ! togglableFields.includes( f.id ) &&
559
+ f.type !== 'media'
465
560
  );
466
561
  const visibleFields = visibleFieldIds
467
562
  .map( ( fieldId ) => fields.find( ( f ) => f.id === fieldId ) )
@@ -471,18 +566,50 @@ function FieldControl() {
471
566
  return null;
472
567
  }
473
568
  const titleField = fields.find( ( f ) => f.id === view.titleField );
474
- const mediaField = fields.find( ( f ) => f.id === view.mediaField );
569
+ const previewField = fields.find( ( f ) => f.id === view.mediaField );
475
570
  const descriptionField = fields.find(
476
571
  ( f ) => f.id === view.descriptionField
477
572
  );
573
+
574
+ const previewFields = fields.filter( ( f ) => f.type === 'media' );
575
+
576
+ let previewFieldUI;
577
+ if ( previewFields.length > 1 ) {
578
+ const isPreviewFieldVisible =
579
+ isDefined( previewField ) && ( view.showMedia ?? true );
580
+ previewFieldUI = isDefined( previewField ) && (
581
+ <FieldItem
582
+ key={ previewField.id }
583
+ field={ previewField }
584
+ label={ __( 'Preview' ) }
585
+ description={ previewField.label }
586
+ isVisible={ isPreviewFieldVisible }
587
+ onToggleVisibility={ () => {
588
+ onChangeView( {
589
+ ...view,
590
+ showMedia: ! isPreviewFieldVisible,
591
+ } );
592
+ } }
593
+ canMove={ false }
594
+ previewOptions={ previewFields.map( ( field ) => ( {
595
+ label: field.label,
596
+ id: field.id,
597
+ } ) ) }
598
+ onChangePreviewOption={ ( newPreviewId ) =>
599
+ onChangeView( { ...view, mediaField: newPreviewId } )
600
+ }
601
+ />
602
+ );
603
+ }
478
604
  const lockedFields = [
479
605
  {
480
606
  field: titleField,
481
607
  isVisibleFlag: 'showTitle',
482
608
  },
483
609
  {
484
- field: mediaField,
610
+ field: previewField,
485
611
  isVisibleFlag: 'showMedia',
612
+ ui: previewFieldUI,
486
613
  },
487
614
  {
488
615
  field: descriptionField,
@@ -493,12 +620,20 @@ function FieldControl() {
493
620
  ( { field, isVisibleFlag } ) =>
494
621
  // @ts-expect-error
495
622
  isDefined( field ) && ( view[ isVisibleFlag ] ?? true )
496
- ) as Array< { field: NormalizedField< any >; isVisibleFlag: string } >;
623
+ ) as Array< {
624
+ field: NormalizedField< any >;
625
+ isVisibleFlag: string;
626
+ ui?: ReactNode;
627
+ } >;
497
628
  const hiddenLockedFields = lockedFields.filter(
498
629
  ( { field, isVisibleFlag } ) =>
499
630
  // @ts-expect-error
500
631
  isDefined( field ) && ! ( view[ isVisibleFlag ] ?? true )
501
- ) as Array< { field: NormalizedField< any >; isVisibleFlag: string } >;
632
+ ) as Array< {
633
+ field: NormalizedField< any >;
634
+ isVisibleFlag: string;
635
+ ui?: ReactNode;
636
+ } >;
502
637
 
503
638
  return (
504
639
  <VStack className="dataviews-field-control" spacing={ 6 }>
@@ -507,20 +642,22 @@ function FieldControl() {
507
642
  !! visibleFields?.length ) && (
508
643
  <ItemGroup isBordered isSeparated>
509
644
  { visibleLockedFields.map(
510
- ( { field, isVisibleFlag } ) => {
645
+ ( { field, isVisibleFlag, ui } ) => {
511
646
  return (
512
- <FieldItem
513
- key={ field.id }
514
- field={ field }
515
- isVisible
516
- onToggleVisibility={ () => {
517
- onChangeView( {
518
- ...view,
519
- [ isVisibleFlag ]: false,
520
- } );
521
- } }
522
- canMove={ false }
523
- />
647
+ ui ?? (
648
+ <FieldItem
649
+ key={ field.id }
650
+ field={ field }
651
+ isVisible
652
+ onToggleVisibility={ () => {
653
+ onChangeView( {
654
+ ...view,
655
+ [ isVisibleFlag ]: false,
656
+ } );
657
+ } }
658
+ canMove={ false }
659
+ />
660
+ )
524
661
  );
525
662
  }
526
663
  ) }
@@ -550,20 +687,23 @@ function FieldControl() {
550
687
  <ItemGroup isBordered isSeparated>
551
688
  { hiddenLockedFields.length > 0 &&
552
689
  hiddenLockedFields.map(
553
- ( { field, isVisibleFlag } ) => {
690
+ ( { field, isVisibleFlag, ui } ) => {
554
691
  return (
555
- <FieldItem
556
- key={ field.id }
557
- field={ field }
558
- isVisible={ false }
559
- onToggleVisibility={ () => {
560
- onChangeView( {
561
- ...view,
562
- [ isVisibleFlag ]: true,
563
- } );
564
- } }
565
- canMove={ false }
566
- />
692
+ ui ?? (
693
+ <FieldItem
694
+ key={ field.id }
695
+ field={ field }
696
+ isVisible={ false }
697
+ onToggleVisibility={ () => {
698
+ onChangeView( {
699
+ ...view,
700
+ [ isVisibleFlag ]:
701
+ true,
702
+ } );
703
+ } }
704
+ canMove={ false }
705
+ />
706
+ )
567
707
  );
568
708
  }
569
709
  ) }
@@ -68,7 +68,8 @@
68
68
  }
69
69
 
70
70
  .dataviews-field-control__field:hover,
71
- .dataviews-field-control__field:focus-within {
71
+ .dataviews-field-control__field:focus-within,
72
+ .dataviews-field-control__field.is-interacting {
72
73
  .dataviews-field-control__actions {
73
74
  position: unset;
74
75
  top: unset;
@@ -80,6 +81,18 @@
80
81
  width: $icon-size;
81
82
  }
82
83
 
83
- .dataviews-field-control__label {
84
+ .dataviews-field-control__label-sub-label-container {
84
85
  flex-grow: 1;
85
86
  }
87
+
88
+ .dataviews-field-control__label {
89
+ display: block;
90
+ }
91
+
92
+ .dataviews-field-control__sub-label {
93
+ margin-top: $grid-unit-10;
94
+ margin-bottom: 0;
95
+ font-size: 11px;
96
+ font-style: normal;
97
+ color: $gray-700;
98
+ }
@@ -2,10 +2,11 @@
2
2
  margin-bottom: auto;
3
3
  grid-template-rows: max-content;
4
4
  padding: 0 $grid-unit-60 $grid-unit-30;
5
- transition: padding ease-out 0.1s;
6
5
  container-type: inline-size;
7
- @include reduce-motion("transition");
8
6
 
7
+ @media not (prefers-reduced-motion) {
8
+ transition: padding ease-out 0.1s;
9
+ }
9
10
 
10
11
  .dataviews-view-grid__card {
11
12
  height: 100%;
@@ -174,7 +174,7 @@ function TableRow< Item >( {
174
174
  </td>
175
175
  ) }
176
176
  { columns.map( ( column: string ) => {
177
- // Explicits picks the supported styles.
177
+ // Explicit picks the supported styles.
178
178
  const { width, maxWidth, minWidth } =
179
179
  view.layout?.styles?.[ column ] ?? {};
180
180
 
@@ -337,7 +337,7 @@ function ViewTable< Item >( {
337
337
  </th>
338
338
  ) }
339
339
  { columns.map( ( column, index ) => {
340
- // Explicits picks the supported styles.
340
+ // Explicit picks the supported styles.
341
341
  const { width, maxWidth, minWidth } =
342
342
  view.layout?.styles?.[ column ] ?? {};
343
343
  return (
@@ -8,7 +8,7 @@ function sort( a: any, b: any, direction: SortDirection ) {
8
8
  }
9
9
 
10
10
  function isValid( value: any, context?: ValidationContext ) {
11
- // TODO: this implicitely means the value is required.
11
+ // TODO: this implicitly means the value is required.
12
12
  if ( value === '' ) {
13
13
  return false;
14
14
  }
package/src/types.ts CHANGED
@@ -42,7 +42,7 @@ export type Operator =
42
42
  | 'isAll'
43
43
  | 'isNotAll';
44
44
 
45
- export type FieldType = 'text' | 'integer' | 'datetime';
45
+ export type FieldType = 'text' | 'integer' | 'datetime' | 'media';
46
46
 
47
47
  export type ValidationContext = {
48
48
  elements?: Option[];