@aquera/nile-elements 1.8.2 → 1.8.3

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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Webcomponent nile-elements following open-wc recommendations",
4
4
  "license": "MIT",
5
5
  "author": "nile-elements",
6
- "version": "1.8.2",
6
+ "version": "1.8.3",
7
7
  "main": "dist/src/index.js",
8
8
  "type": "module",
9
9
  "module": "dist/src/index.js",
@@ -154,6 +154,41 @@ export const styles = css`
154
154
  padding: 0;
155
155
  }
156
156
 
157
+ /* ── Preview state ──────────────────────────────────────────────────────── */
158
+
159
+ .detail__body--preview {
160
+ position: relative;
161
+ cursor: pointer;
162
+ }
163
+
164
+ .detail__body--preview::after {
165
+ content: '';
166
+ position: absolute;
167
+ left: 0;
168
+ right: 0;
169
+ bottom: 0;
170
+ height: var(--nile-detail-preview-fade-height, 40px);
171
+ background: linear-gradient(
172
+ to bottom,
173
+ transparent,
174
+ var(--nile-colors-white-base, var(--ng-colors-bg-primary))
175
+ );
176
+ pointer-events: none;
177
+ }
178
+
179
+ .detail__body--preview:focus {
180
+ outline: none;
181
+ }
182
+
183
+ .detail__body--preview:focus-visible {
184
+ outline: solid 3px var(--nile-colors-blue-500, var(--ng-colors-fg-brand-primary-600));
185
+ outline-offset: -3px;
186
+ }
187
+
188
+ .detail--disabled .detail__body--preview {
189
+ cursor: not-allowed;
190
+ }
191
+
157
192
  /* ── Selection variant ──────────────────────────────────────────────────── */
158
193
 
159
194
  .detail__selection-label {
@@ -410,7 +445,8 @@ export const styles = css`
410
445
  var(--nile-spacing-3xl, var(--ng-spacing-3xl));
411
446
  }
412
447
 
413
- .detail--light.detail--open .detail__header {
448
+ .detail--light.detail--open .detail__header,
449
+ .detail--light.detail--preview .detail__header {
414
450
  border-bottom: 1px solid var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
415
451
  }
416
452
 
@@ -12,6 +12,7 @@ import {
12
12
  hideDetail,
13
13
  handleSummaryClick,
14
14
  handleSummaryKeyDown,
15
+ getPreviewHeight,
15
16
  } from './nile-detail.utils';
16
17
  import '../nile-checkbox/index';
17
18
  import '../nile-input/index';
@@ -104,6 +105,10 @@ export interface NileDetailSelectionConfig {
104
105
 
105
106
  showSelectedToggle?: boolean;
106
107
 
108
+ // Preview state
109
+ preview?: boolean;
110
+ previewPercentage?: number;
111
+
107
112
  // Inherited / passthrough
108
113
  open?: boolean;
109
114
  disabled?: boolean;
@@ -128,6 +133,12 @@ export class NileDetail extends NileElement {
128
133
 
129
134
  @property({ attribute: true, type: Boolean, reflect: true }) disabled = false;
130
135
 
136
+ /** When set, the closed state shows a preview of the body content instead of hiding it. */
137
+ @property({ attribute: true, type: Boolean, reflect: true }) preview = false;
138
+
139
+ /** Percentage (0–100) of the content height shown while previewing. */
140
+ @property({ attribute: 'preview-percentage', type: Number, reflect: true }) previewPercentage = 30;
141
+
131
142
  @property({ attribute: true, type: String, reflect: true }) variant: NileDetailVariant = 'default';
132
143
 
133
144
  @property({ attribute: false, type: Array }) items: SelectionItem[] = [];
@@ -218,6 +229,16 @@ export class NileDetail extends NileElement {
218
229
  return this.variant === 'light' || this.variant === 'select-light';
219
230
  }
220
231
 
232
+ /** True when preview mode is on with a usable percentage. */
233
+ private get _previewEnabled(): boolean {
234
+ return this.preview && Math.min(100, Math.max(0, this.previewPercentage)) > 0;
235
+ }
236
+
237
+ /** True when the closed state should currently render the preview. */
238
+ private get _isPreviewActive(): boolean {
239
+ return this._previewEnabled && !this.open;
240
+ }
241
+
221
242
  private get _isInfiniteMode(): boolean {
222
243
  return (
223
244
  this._isSelectionVariant &&
@@ -308,9 +329,12 @@ export class NileDetail extends NileElement {
308
329
  }
309
330
 
310
331
  firstUpdated() {
311
- this._detailOpen = this.open;
312
- this.body.hidden = !this.open;
332
+ this._detailOpen = this.open || this._isPreviewActive;
333
+ this.body.hidden = !this.open && !this._isPreviewActive;
313
334
  this.body.style.height = this.open ? 'auto' : '0';
335
+ if (this._isPreviewActive) {
336
+ this._applyPreviewHeight();
337
+ }
314
338
  this._syncSelectedFromProperty();
315
339
  if (this._restoreDefaults === null) {
316
340
  this._restoreDefaults = [...this._selectedSet];
@@ -541,6 +565,9 @@ export class NileDetail extends NileElement {
541
565
  }
542
566
 
543
567
  if (c.showSelectedToggle !== undefined) this.showSelectedToggle = c.showSelectedToggle;
568
+
569
+ if (c.preview !== undefined) this.preview = c.preview;
570
+ if (c.previewPercentage !== undefined) this.previewPercentage = c.previewPercentage;
544
571
  }
545
572
 
546
573
  private _syncSelectedFromProperty() {
@@ -728,6 +755,9 @@ export class NileDetail extends NileElement {
728
755
  if (node.nodeType === Node.TEXT_NODE) return !!(node.textContent && node.textContent.trim());
729
756
  return false;
730
757
  });
758
+ if (this._isPreviewActive) {
759
+ this._applyPreviewHeight();
760
+ }
731
761
  }
732
762
 
733
763
  private _stopHeaderToggle(event: Event) {
@@ -750,10 +780,52 @@ export class NileDetail extends NileElement {
750
780
  await animateShow(this);
751
781
  } else {
752
782
  await animateHide(this);
783
+ // In preview mode the details element stays open so the preview remains visible.
784
+ this._detailOpen = this._previewEnabled;
785
+ }
786
+ }
787
+
788
+ @watch(['preview', 'previewPercentage'], { waitUntilFirstUpdate: true })
789
+ handlePreviewChange() {
790
+ if (this.open) {
791
+ return;
792
+ }
793
+ if (this._previewEnabled) {
794
+ this._detailOpen = true;
795
+ this._applyPreviewHeight();
796
+ } else {
797
+ this.body.hidden = true;
798
+ this.body.style.height = '0';
753
799
  this._detailOpen = false;
754
800
  }
755
801
  }
756
802
 
803
+ /** Measures the content and clamps the body to the configured preview height. */
804
+ private async _applyPreviewHeight() {
805
+ await this.updateComplete;
806
+ if (!this._isPreviewActive || !this.body) {
807
+ return;
808
+ }
809
+ this.body.hidden = false;
810
+ this.body.style.height = `${getPreviewHeight(this)}px`;
811
+ }
812
+
813
+ private _handlePreviewClick() {
814
+ if (this._isPreviewActive && !this.disabled) {
815
+ showDetail(this);
816
+ }
817
+ }
818
+
819
+ private _handlePreviewKeyDown(event: KeyboardEvent) {
820
+ if (!this._isPreviewActive || this.disabled) {
821
+ return;
822
+ }
823
+ if (event.key === 'Enter' || event.key === ' ') {
824
+ event.preventDefault();
825
+ showDetail(this);
826
+ }
827
+ }
828
+
757
829
  async show() {
758
830
  return showDetail(this);
759
831
  }
@@ -1017,6 +1089,7 @@ export class NileDetail extends NileElement {
1017
1089
  'detail--disabled': this.disabled,
1018
1090
  'detail--selection': isSelection,
1019
1091
  'detail--light': this._isLightVariant,
1092
+ 'detail--preview': this._isPreviewActive,
1020
1093
  })}
1021
1094
  >
1022
1095
  <summary
@@ -1062,7 +1135,18 @@ export class NileDetail extends NileElement {
1062
1135
 
1063
1136
  </summary>
1064
1137
 
1065
- <div part="content" class="detail__body">
1138
+ <div
1139
+ part="content"
1140
+ class=${classMap({
1141
+ 'detail__body': true,
1142
+ 'detail__body--preview': this._isPreviewActive,
1143
+ })}
1144
+ role=${this._isPreviewActive ? 'button' : nothing}
1145
+ tabindex=${this._isPreviewActive && !this.disabled ? '0' : nothing}
1146
+ aria-label=${this._isPreviewActive ? 'Expand to view full content' : nothing}
1147
+ @click=${this._handlePreviewClick}
1148
+ @keydown=${this._handlePreviewKeyDown}
1149
+ >
1066
1150
  ${isSelection
1067
1151
  ? html`<div part="selection-content" class="detail__selection-content">${this._renderSelectionBody()}</div>`
1068
1152
  : ''}
@@ -2,9 +2,20 @@ import { animateTo, shimKeyframesHeightAuto, stopAnimations } from '../internal/
2
2
  import { getAnimation, setDefaultAnimation } from '../utilities/animation-registry';
3
3
  import { waitForEvent } from '../internal/event';
4
4
  import type { NileDetail } from './nile-detail';
5
+ export function getPreviewHeight(component: NileDetail): number {
6
+ if (!component.preview) {
7
+ return 0;
8
+ }
9
+ const pct = Math.min(100, Math.max(0, component.previewPercentage));
10
+ if (pct <= 0) {
11
+ return 0;
12
+ }
13
+ return Math.max(1, Math.round((component.body.scrollHeight * pct) / 100));
14
+ }
5
15
 
6
16
  /*
7
17
  Runs the show animation on the detail body.
18
+ In preview mode the animation starts from the preview height instead of 0.
8
19
  Emits nile-show (cancelable) before and nile-after-show after the animation.
9
20
  */
10
21
  export async function animateShow(component: NileDetail): Promise<void> {
@@ -17,8 +28,15 @@ export async function animateShow(component: NileDetail): Promise<void> {
17
28
  await stopAnimations(component.body);
18
29
  component.body.hidden = false;
19
30
 
31
+ const previewHeight = getPreviewHeight(component);
20
32
  const { keyframes, options } = getAnimation(component, 'detail.show', { dir: 'ltr' });
21
- await animateTo(component.body, shimKeyframesHeightAuto(keyframes, component.body.scrollHeight), options);
33
+ const frames = previewHeight > 0
34
+ ? [
35
+ { height: `${previewHeight}px`, opacity: '1' },
36
+ { height: 'auto', opacity: '1' }
37
+ ]
38
+ : keyframes;
39
+ await animateTo(component.body, shimKeyframesHeightAuto(frames, component.body.scrollHeight), options);
22
40
  component.body.style.height = 'auto';
23
41
 
24
42
  component.emit('nile-after-show');
@@ -26,6 +44,7 @@ export async function animateShow(component: NileDetail): Promise<void> {
26
44
 
27
45
  /*
28
46
  Runs the hide animation on the detail body.
47
+ In preview mode the body collapses to the preview height and stays visible.
29
48
  Emits nile-hide (cancelable) before and nile-after-hide after the animation.
30
49
  */
31
50
  export async function animateHide(component: NileDetail): Promise<void> {
@@ -37,10 +56,21 @@ export async function animateHide(component: NileDetail): Promise<void> {
37
56
 
38
57
  await stopAnimations(component.body);
39
58
 
59
+ const previewHeight = getPreviewHeight(component);
40
60
  const { keyframes, options } = getAnimation(component, 'detail.hide', { dir: 'ltr' });
41
- await animateTo(component.body, shimKeyframesHeightAuto(keyframes, component.body.scrollHeight), options);
42
- component.body.hidden = true;
43
- component.body.style.height = 'auto';
61
+
62
+ if (previewHeight > 0) {
63
+ const frames = [
64
+ { height: 'auto', opacity: '1' },
65
+ { height: `${previewHeight}px`, opacity: '1' }
66
+ ];
67
+ await animateTo(component.body, shimKeyframesHeightAuto(frames, component.body.scrollHeight), options);
68
+ component.body.style.height = `${previewHeight}px`;
69
+ } else {
70
+ await animateTo(component.body, shimKeyframesHeightAuto(keyframes, component.body.scrollHeight), options);
71
+ component.body.hidden = true;
72
+ component.body.style.height = 'auto';
73
+ }
44
74
 
45
75
  component.emit('nile-after-hide');
46
76
  }
@@ -1896,7 +1896,7 @@
1896
1896
  },
1897
1897
  {
1898
1898
  "name": "nile-detail",
1899
- "description": "Events:\n\n * `nile-page-load` {`CustomEvent<{ pageIndex: number; offset: number; limit: number; rows: any; }>`} - \n\n * `nile-page-error` {`CustomEvent<{ pageIndex: number; offset: number; limit: number; error: any; }>`} - \n\n * `nile-change` {`CustomEvent<{ selected: string[]; }>`} - \n\n * `nile-search` {`CustomEvent<{ value: string; }>`} - \n\nAttributes:\n\n * `open` {`boolean`} - \n\n * `heading` {`string`} - \n\n * `description` {`string`} - \n\n * `expandIconPlacement` {`\"left\" | \"right\"`} - \n\n * `disabled` {`boolean`} - \n\n * `variant` {`NileDetailVariant`} - \n\n * `allow-html-label` {`boolean`} - \n\n * `disable-local-search` {`boolean`} - \n\n * `total-count` {`number | undefined`} - \n\n * `page-size` {`number`} - \n\n * `placeholder-label` {`string`} - \n\n * `search-placeholder` {`string`} - \n\n * `items-label` {`string`} - \n\n * `restore-label` {`string`} - \n\n * `clear-label` {`string`} - \n\n * `grid-rows` {`number`} - \n\n * `grid-columns` {`number`} - \n\n * `min-column-width` {`string`} - \n\n * `lane-height` {`number`} - \n\n * `orientation` {`\"vertical\" | \"horizontal\" | \"both\"`} - \n\n * `max-height` {`string`} - \n\n * `matrix-columns` {`number`} - \n\n * `virtualize` {`boolean`} - \n\n * `virtualize-threshold` {`number`} - \n\n * `overscan` {`number`} - \n\n * `show-selected-toggle` {`boolean`} - \n\n * `selected-only-label` {`string`} - \n\n * `show-all-label` {`string`} - \n\nProperties:\n\n * `styles` - \n\n * `detail` {`HTMLDetailsElement`} - \n\n * `header` {`HTMLElement`} - \n\n * `body` {`HTMLElement`} - \n\n * `open` {`boolean`} - \n\n * `heading` {`string`} - \n\n * `description` {`string`} - \n\n * `expandIconPlacement` {`\"left\" | \"right\"`} - \n\n * `disabled` {`boolean`} - \n\n * `variant` {`NileDetailVariant`} - \n\n * `items` {`SelectionItem[]`} - \n\n * `selected` {`string[]`} - \n\n * `renderItemConfig` {`NileDetailRenderItemConfig | undefined`} - \n\n * `allowHtmlLabel` {`boolean`} - \n\n * `disableLocalSearch` {`boolean`} - \n\n * `totalCount` {`number | undefined`} - \n\n * `pageSize` {`number`} - \n\n * `fetchPage` - \n\n * `placeholderLabel` {`string`} - \n\n * `config` {`NileDetailSelectionConfig | undefined`} - \n\n * `searchPlaceholder` {`string`} - \n\n * `itemsLabel` {`string`} - \n\n * `restoreLabel` {`string`} - \n\n * `clearLabel` {`string`} - \n\n * `gridRows` {`number`} - \n\n * `gridColumns` {`number`} - \n\n * `minColumnWidth` {`string`} - \n\n * `laneHeight` {`number`} - \n\n * `orientation` {`\"vertical\" | \"horizontal\" | \"both\"`} - \n\n * `maxHeight` {`string`} - \n\n * `matrixColumns` {`number`} - \n\n * `virtualize` {`boolean`} - \n\n * `virtualizeThreshold` {`number`} - \n\n * `overscan` {`number`} - \n\n * `_detailOpen` {`boolean`} - \n\n * `_hasSlottedContent` {`boolean`} - \n\n * `_searchTerm` {`string`} - \n\n * `_selectedSet` {`Set<string>`} - \n\n * `_showSelectedOnly` {`boolean`} - \n\n * `showSelectedToggle` {`boolean`} - \n\n * `selectedOnlyLabel` {`string`} - \n\n * `showAllLabel` {`string`} - \n\n * `_restoreDefaults` {`string[] | null`} - \n\n * `_gridResizeObserver` - \n\n * `_virtCtrl` - \n\n * `_rowVirtCtrl` - \n\n * `_colVirtCtrl` - \n\n * `_pageCache` - \n\n * `_pendingPages` - \n\n * `_isSelectionVariant` {`boolean`} - True for any variant that renders the selection UI/behavior.\n\n * `_isLightVariant` {`boolean`} - True for any variant that applies the light styling.\n\n * `_isInfiniteMode` {`boolean`} - \n\n * `_isVirtualized` {`boolean`} - \n\n * `_columnWidthPx` {`number`} - \n\n * `_filteredItems` {`SelectionItem[]`} - \n\n * `_allChecked` {`boolean`} - \n\n * `_indeterminate` {`boolean`} - \n\n * `_countLabel` {`string`} - \n\n * `_summaryLabel` {`string`} - \n\n * `BUBBLES` {`boolean`} - \n\n * `COMPOSED` {`boolean`} - \n\n * `CANCELABLE` {`boolean`} - ",
1899
+ "description": "Events:\n\n * `nile-page-load` {`CustomEvent<{ pageIndex: number; offset: number; limit: number; rows: any; }>`} - \n\n * `nile-page-error` {`CustomEvent<{ pageIndex: number; offset: number; limit: number; error: any; }>`} - \n\n * `nile-change` {`CustomEvent<{ selected: string[]; }>`} - \n\n * `nile-search` {`CustomEvent<{ value: string; }>`} - \n\nAttributes:\n\n * `open` {`boolean`} - \n\n * `heading` {`string`} - \n\n * `description` {`string`} - \n\n * `expandIconPlacement` {`\"left\" | \"right\"`} - \n\n * `disabled` {`boolean`} - \n\n * `preview` {`boolean`} - When set, the closed state shows a preview of the body content instead of hiding it.\n\n * `preview-percentage` {`number`} - Percentage (0–100) of the content height shown while previewing.\n\n * `variant` {`NileDetailVariant`} - \n\n * `allow-html-label` {`boolean`} - \n\n * `disable-local-search` {`boolean`} - \n\n * `total-count` {`number | undefined`} - \n\n * `page-size` {`number`} - \n\n * `placeholder-label` {`string`} - \n\n * `search-placeholder` {`string`} - \n\n * `items-label` {`string`} - \n\n * `restore-label` {`string`} - \n\n * `clear-label` {`string`} - \n\n * `grid-rows` {`number`} - \n\n * `grid-columns` {`number`} - \n\n * `min-column-width` {`string`} - \n\n * `lane-height` {`number`} - \n\n * `orientation` {`\"vertical\" | \"horizontal\" | \"both\"`} - \n\n * `max-height` {`string`} - \n\n * `matrix-columns` {`number`} - \n\n * `virtualize` {`boolean`} - \n\n * `virtualize-threshold` {`number`} - \n\n * `overscan` {`number`} - \n\n * `show-selected-toggle` {`boolean`} - \n\n * `selected-only-label` {`string`} - \n\n * `show-all-label` {`string`} - \n\nProperties:\n\n * `styles` - \n\n * `detail` {`HTMLDetailsElement`} - \n\n * `header` {`HTMLElement`} - \n\n * `body` {`HTMLElement`} - \n\n * `open` {`boolean`} - \n\n * `heading` {`string`} - \n\n * `description` {`string`} - \n\n * `expandIconPlacement` {`\"left\" | \"right\"`} - \n\n * `disabled` {`boolean`} - \n\n * `preview` {`boolean`} - When set, the closed state shows a preview of the body content instead of hiding it.\n\n * `previewPercentage` {`number`} - Percentage (0–100) of the content height shown while previewing.\n\n * `variant` {`NileDetailVariant`} - \n\n * `items` {`SelectionItem[]`} - \n\n * `selected` {`string[]`} - \n\n * `renderItemConfig` {`NileDetailRenderItemConfig | undefined`} - \n\n * `allowHtmlLabel` {`boolean`} - \n\n * `disableLocalSearch` {`boolean`} - \n\n * `totalCount` {`number | undefined`} - \n\n * `pageSize` {`number`} - \n\n * `fetchPage` - \n\n * `placeholderLabel` {`string`} - \n\n * `config` {`NileDetailSelectionConfig | undefined`} - \n\n * `searchPlaceholder` {`string`} - \n\n * `itemsLabel` {`string`} - \n\n * `restoreLabel` {`string`} - \n\n * `clearLabel` {`string`} - \n\n * `gridRows` {`number`} - \n\n * `gridColumns` {`number`} - \n\n * `minColumnWidth` {`string`} - \n\n * `laneHeight` {`number`} - \n\n * `orientation` {`\"vertical\" | \"horizontal\" | \"both\"`} - \n\n * `maxHeight` {`string`} - \n\n * `matrixColumns` {`number`} - \n\n * `virtualize` {`boolean`} - \n\n * `virtualizeThreshold` {`number`} - \n\n * `overscan` {`number`} - \n\n * `_detailOpen` {`boolean`} - \n\n * `_hasSlottedContent` {`boolean`} - \n\n * `_searchTerm` {`string`} - \n\n * `_selectedSet` {`Set<string>`} - \n\n * `_showSelectedOnly` {`boolean`} - \n\n * `showSelectedToggle` {`boolean`} - \n\n * `selectedOnlyLabel` {`string`} - \n\n * `showAllLabel` {`string`} - \n\n * `_restoreDefaults` {`string[] | null`} - \n\n * `_gridResizeObserver` - \n\n * `_virtCtrl` - \n\n * `_rowVirtCtrl` - \n\n * `_colVirtCtrl` - \n\n * `_pageCache` - \n\n * `_pendingPages` - \n\n * `_isSelectionVariant` {`boolean`} - True for any variant that renders the selection UI/behavior.\n\n * `_isLightVariant` {`boolean`} - True for any variant that applies the light styling.\n\n * `_previewEnabled` {`boolean`} - True when preview mode is on with a usable percentage.\n\n * `_isPreviewActive` {`boolean`} - True when the closed state should currently render the preview.\n\n * `_isInfiniteMode` {`boolean`} - \n\n * `_isVirtualized` {`boolean`} - \n\n * `_columnWidthPx` {`number`} - \n\n * `_filteredItems` {`SelectionItem[]`} - \n\n * `_allChecked` {`boolean`} - \n\n * `_indeterminate` {`boolean`} - \n\n * `_countLabel` {`string`} - \n\n * `_summaryLabel` {`string`} - \n\n * `BUBBLES` {`boolean`} - \n\n * `COMPOSED` {`boolean`} - \n\n * `CANCELABLE` {`boolean`} - ",
1900
1900
  "attributes": [
1901
1901
  {
1902
1902
  "name": "open",
@@ -1928,6 +1928,15 @@
1928
1928
  "description": "`disabled` {`boolean`} - \n\nProperty: disabled\n\nDefault: false",
1929
1929
  "valueSet": "v"
1930
1930
  },
1931
+ {
1932
+ "name": "preview",
1933
+ "description": "`preview` {`boolean`} - When set, the closed state shows a preview of the body content instead of hiding it.\n\nProperty: preview\n\nDefault: false",
1934
+ "valueSet": "v"
1935
+ },
1936
+ {
1937
+ "name": "preview-percentage",
1938
+ "description": "`preview-percentage` {`number`} - Percentage (0–100) of the content height shown while previewing.\n\nProperty: previewPercentage\n\nDefault: 30"
1939
+ },
1931
1940
  {
1932
1941
  "name": "variant",
1933
1942
  "description": "`variant` {`NileDetailVariant`} - \n\nProperty: variant\n\nDefault: default",