@inglorious/ui 9.10.0 → 9.11.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inglorious/ui",
3
- "version": "9.10.0",
3
+ "version": "9.11.0",
4
4
  "description": "A design system for Inglorious Web with render-function components and customizable themes",
5
5
  "author": "Matteo Antony Mistretta <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
6
6
  "license": "MIT",
@@ -354,6 +354,7 @@
354
354
  "@inglorious/vite-plugin-jsx": "2.0.6",
355
355
  "@inglorious/vite-plugin-vue": "1.0.7",
356
356
  "@inglorious/web": "6.14.0",
357
+ "@lit-labs/ssr": "^4.1.0",
357
358
  "@storybook/addon-docs": "^10.6.5",
358
359
  "@storybook/web-components-vite": "^10.6.5",
359
360
  "happy-dom": "^20.14.0",
@@ -9,6 +9,7 @@ import { ref } from "@inglorious/web/directives/ref"
9
9
  import { when } from "@inglorious/web/directives/when"
10
10
 
11
11
  import { applyElementProps } from "../../shared/apply-element-props.js"
12
+ import { liveBoolean } from "../../shared/live-boolean.js"
12
13
 
13
14
  /**
14
15
  * Renders a labeled checkbox for boolean input.
@@ -51,7 +52,8 @@ export function render(props) {
51
52
  class="iw-checkbox-input"
52
53
  type="checkbox"
53
54
  name=${name}
54
- .checked=${isChecked}
55
+ ?checked=${isChecked}
56
+ ${liveBoolean("checked", isChecked)}
55
57
  ?disabled=${isDisabled}
56
58
  ?required=${isRequired}
57
59
  @change=${(event) => onChange?.(event.target.checked)}
@@ -46,6 +46,7 @@ export function open(entity) {
46
46
  /** @param {SelectEntity} entity */
47
47
  export function close(entity) {
48
48
  entity.isOpen = false
49
+ entity.searchTerm = ""
49
50
  entity.focusedIndex = NONE
50
51
  }
51
52
 
@@ -67,7 +68,7 @@ export function optionSelect(entity, option, api) {
67
68
  if (normalizedOption.isDisabled) return
68
69
  const optionValue = getOptionValue(normalizedOption)
69
70
 
70
- if (normalizedOption.isCreated) {
71
+ if (normalizedOption.isCreated || entity.searchTerm) {
71
72
  entity.searchTerm = ""
72
73
  entity.focusedIndex = NONE
73
74
  }
@@ -12,6 +12,7 @@ import { when } from "@inglorious/web/directives/when"
12
12
 
13
13
  import { Chip } from "../../data-display/chip/index.js"
14
14
  import { fallbackLabel } from "../../shared/fallback-label.js"
15
+ import { liveBoolean } from "../../shared/live-boolean.js"
15
16
  import { syncPopover } from "../../shared/top-layer.js"
16
17
  import {
17
18
  formatOption,
@@ -384,7 +385,8 @@ export const Combobox = {
384
385
  () =>
385
386
  html`<input
386
387
  type="checkbox"
387
- .checked=${isSelected}
388
+ ?checked=${isSelected}
389
+ ${liveBoolean("checked", isSelected)}
388
390
  ?disabled=${!!normalized.isDisabled}
389
391
  />`,
390
392
  )}
@@ -9,6 +9,7 @@ import { ref } from "@inglorious/web/directives/ref"
9
9
  import { when } from "@inglorious/web/directives/when"
10
10
 
11
11
  import { applyElementProps } from "../../shared/apply-element-props.js"
12
+ import { liveBoolean } from "../../shared/live-boolean.js"
12
13
 
13
14
  /**
14
15
  * Renders a group of radio options for single selection.
@@ -72,7 +73,8 @@ export function render(props) {
72
73
  type="radio"
73
74
  name=${groupName || nothing}
74
75
  value=${optionValue}
75
- .checked=${value === optionValue}
76
+ ?checked=${value === optionValue}
77
+ ${liveBoolean("checked", value === optionValue)}
76
78
  ?disabled=${isDisabled || optionDisabled}
77
79
  @change=${(event) => onChange?.(event.target.value)}
78
80
  ${ref((element) => applyElementProps(element, optionRest))}
@@ -6,6 +6,8 @@
6
6
  import { html, nothing } from "@inglorious/web"
7
7
  import { classMap } from "@inglorious/web/directives/class-map"
8
8
 
9
+ import { liveBoolean } from "../../shared/live-boolean.js"
10
+
9
11
  const DEFAULT_VALUE = 0
10
12
  const DEFAULT_MAX = 5
11
13
  const INDEX_TO_RATING = 1
@@ -62,7 +64,8 @@ export function render(props) {
62
64
  name=${groupName || nothing}
63
65
  value=${ratingValue}
64
66
  aria-label=${`${ratingValue} of ${max}`}
65
- .checked=${ratingValue === value}
67
+ ?checked=${ratingValue === value}
68
+ ${liveBoolean("checked", ratingValue === value)}
66
69
  @change=${() => onChange?.(ratingValue)}
67
70
  />
68
71
  <span aria-hidden="true">${isActive ? symbol : emptySymbol}</span>
@@ -10,6 +10,7 @@ import { when } from "@inglorious/web/directives/when"
10
10
 
11
11
  import { Icon } from "../../data-display/icon/index.js"
12
12
  import { fallbackLabel } from "../../shared/fallback-label.js"
13
+ import { liveBoolean } from "../../shared/live-boolean.js"
13
14
  import { formatOption, getOptionValue, isOptionSelected } from "./helpers.js"
14
15
 
15
16
  /**
@@ -111,10 +112,6 @@ export const Select = {
111
112
  "iw-select-control-error": !!error,
112
113
  }
113
114
 
114
- const selectedOption = options.find(
115
- (option) => getOptionValue(option) === value,
116
- )
117
-
118
115
  return html`<div class="iw-select-frame" @click=${openFromArrowStrip}>
119
116
  ${when(icon, () => Icon.render({ children: icon, size }))}
120
117
 
@@ -137,15 +134,11 @@ export const Select = {
137
134
  (option) => getOptionValue(option),
138
135
  (option) => {
139
136
  const {
140
- value,
137
+ value: optionValue,
141
138
  label,
142
139
  isDisabled: isOptionDisabled,
143
140
  } = formatOption(option)
144
- const isSelected = isOptionSelected(
145
- option,
146
- selectedOption?.value,
147
- isMulti,
148
- )
141
+ const isSelected = isOptionSelected(option, value, isMulti)
149
142
 
150
143
  const optionClasses = {
151
144
  "iw-select-option": true,
@@ -153,8 +146,9 @@ export const Select = {
153
146
  }
154
147
 
155
148
  return html`<option
156
- value=${value}
157
- .selected=${value === selectedOption?.value}
149
+ value=${optionValue}
150
+ ?selected=${isSelected}
151
+ ${liveBoolean("selected", isSelected)}
158
152
  ?disabled=${isOptionDisabled}
159
153
  class=${classMap(optionClasses)}
160
154
  >
@@ -9,6 +9,7 @@ import { ref } from "@inglorious/web/directives/ref"
9
9
  import { when } from "@inglorious/web/directives/when"
10
10
 
11
11
  import { applyElementProps } from "../../shared/apply-element-props.js"
12
+ import { liveBoolean } from "../../shared/live-boolean.js"
12
13
 
13
14
  /**
14
15
  * Renders a toggle switch backed by a checkbox input, which `role="switch"`
@@ -49,7 +50,8 @@ export function render(props) {
49
50
  type="checkbox"
50
51
  role="switch"
51
52
  name=${name}
52
- .checked=${isChecked}
53
+ ?checked=${isChecked}
54
+ ${liveBoolean("checked", isChecked)}
53
55
  ?disabled=${isDisabled}
54
56
  @change=${(event) => onChange?.(event.target.checked)}
55
57
  ${ref((element) => applyElementProps(element, rest))}
@@ -13,6 +13,7 @@
13
13
  color: var(--iw-badge-on-accent);
14
14
  font-size: var(--iw-font-size-xs, 0.75rem);
15
15
  line-height: 1;
16
+ white-space: nowrap;
16
17
 
17
18
  &.iw-badge-sm {
18
19
  --iw-badge-shape-size: 0.5rem;
@@ -433,6 +433,8 @@ function initDataGrid(entity) {
433
433
  entity.isMultiSelect ??= true
434
434
  entity.isVirtualized ??= false
435
435
 
436
+ entity.totalRows ??= null
437
+
436
438
  entity.pagination ??= null
437
439
  if (entity.pagination) {
438
440
  entity.pagination.page ??= 0
@@ -60,11 +60,15 @@ export function getEditedRows(entity) {
60
60
  }
61
61
 
62
62
  /**
63
- * Gets the processed rows (filtered, searched, sorted, paginated).
63
+ * Gets the processed rows (filtered, searched, sorted, paginated). A grid with
64
+ * `totalRows` holds the server's page already processed, so it gets the rows
65
+ * as they are.
64
66
  * @param {TableEntity} entity
65
67
  * @returns {any[]}
66
68
  */
67
69
  export function getRows(entity) {
70
+ if (entity.totalRows != null) return entity.rows
71
+
68
72
  let rows = entity.rows
69
73
  rows = applyFilters(entity, rows)
70
74
  rows = applySearch(entity, rows)
@@ -75,11 +79,14 @@ export function getRows(entity) {
75
79
  }
76
80
 
77
81
  /**
78
- * Gets the total number of rows after filtering and searching.
82
+ * Gets the total number of rows after filtering and searching, or the
83
+ * server's `totalRows` when the grid has it.
79
84
  * @param {TableEntity} entity
80
85
  * @returns {number}
81
86
  */
82
87
  export function getTotalRows(entity) {
88
+ if (entity.totalRows != null) return entity.totalRows
89
+
83
90
  let rows = entity.rows
84
91
  rows = applyFilters(entity, rows)
85
92
  rows = applySearch(entity, rows)
@@ -0,0 +1,17 @@
1
+ import { ref } from "@inglorious/web/directives/ref"
2
+
3
+ /**
4
+ * Keeps an element's `checked` or `selected` property in step with the state.
5
+ *
6
+ * A `.checked=${false}` binding can't be used instead: server rendering
7
+ * reflects it as `checked="false"`, which HTML reads as checked. Templates pair
8
+ * this with the boolean attribute (`?checked=${value}`), which renders right on
9
+ * the server but stops counting once the user has touched the control.
10
+ * @param {"checked" | "selected"} name
11
+ * @param {boolean} value
12
+ */
13
+ export function liveBoolean(name, value) {
14
+ return ref((element) => {
15
+ if (element) element[name] = value
16
+ })
17
+ }
@@ -75,6 +75,13 @@ export interface DataGridProps<T extends Row = Row> {
75
75
  selectionAnchor?: string | number | null
76
76
  pagination?: { page: number; pageSize: number; pageSizes: number[] } | null
77
77
  rowId?: string
78
+ /**
79
+ * How many rows the server has for the current filters and search. When it
80
+ * is set, `rows` is the page the server already filtered, sorted and sliced,
81
+ * so the grid shows them as they are and pages by this total. Set it to `0`
82
+ * before the first load to start the grid server-side.
83
+ */
84
+ totalRows?: number | null
78
85
  isMultiSelect: boolean
79
86
  /**
80
87
  * The user's unsaved edits, by row key and column id, shown over the rows