@inglorious/ui 9.3.0 → 9.4.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.3.0",
3
+ "version": "9.4.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",
@@ -315,7 +315,7 @@
315
315
  "@iconify-icons/fa7-regular": "^2.0.0",
316
316
  "@iconify-icons/fa7-solid": "^2.0.0",
317
317
  "@iconify-icons/material-symbols": "^2.0.6",
318
- "@inglorious/charts": "12.2.0",
318
+ "@inglorious/charts": "12.2.1",
319
319
  "@inglorious/eslint-config": "1.2.0",
320
320
  "@inglorious/store": "12.0.0",
321
321
  "@inglorious/vite-plugin-jsx": "2.0.5",
@@ -35,9 +35,16 @@
35
35
  gap: var(--iw-space-2);
36
36
  }
37
37
 
38
- .iw-data-grid-filter-range-input {
39
- min-width: 0;
38
+ /* The two ends of a range share the column; native date and number
39
+ fields would otherwise keep a width of their own and spill out. */
40
+ .iw-data-grid-filter-range > .iw-input {
40
41
  flex: 1 1 0;
42
+ min-width: 0;
43
+
44
+ .iw-input-frame,
45
+ .iw-input-control {
46
+ min-width: 0;
47
+ }
41
48
  }
42
49
 
43
50
  .iw-data-grid-row {
@@ -19,7 +19,6 @@ export const RangeFilter = {
19
19
  placeholder: column.filter.placeholder ?? "≥",
20
20
  size: "sm",
21
21
  isFullWidth: true,
22
- className: "iw-data-grid-filter-range-input",
23
22
  onChange: (value) => {
24
23
  const formattedValue = value ? format(value, column.type) : null
25
24
  entity.onFilterChange?.({
@@ -34,7 +33,6 @@ export const RangeFilter = {
34
33
  placeholder: column.filter.placeholder ?? "≤",
35
34
  size: "sm",
36
35
  isFullWidth: true,
37
- className: "iw-data-grid-filter-range-input",
38
36
  onChange: (value) => {
39
37
  const formattedValue = value ? format(value, column.type) : null
40
38
  entity.onFilterChange?.({
@@ -302,7 +302,7 @@ export function cellChange(entity, { rowId, columnId, value }) {
302
302
 
303
303
  entity.edits ??= {}
304
304
  const edits = (entity.edits[rowId] ??= {})
305
- if (Object.is(row[columnId], value)) {
305
+ if (isSameValue(row[columnId], value)) {
306
306
  delete edits[columnId]
307
307
  } else {
308
308
  edits[columnId] = value
@@ -500,7 +500,7 @@ function getDefaultColumnFilter(type) {
500
500
  */
501
501
  function getDefaultColumnWidth(filterType) {
502
502
  if (filterType === "number") return 70
503
- if (filterType === "range") return 120
503
+ if (filterType === "range") return 160
504
504
  if (filterType === "select") return 70
505
505
  if (filterType === "date") return 120
506
506
  if (filterType === "time") return 120
@@ -564,3 +564,14 @@ function assertRowKeys(entity) {
564
564
  seen.add(rowKey)
565
565
  })
566
566
  }
567
+
568
+ /**
569
+ * Whether two cell values are the same, dates by their time.
570
+ * @param {any} a
571
+ * @param {any} b
572
+ * @returns {boolean}
573
+ */
574
+ function isSameValue(a, b) {
575
+ if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime()
576
+ return Object.is(a, b)
577
+ }
@@ -33,6 +33,13 @@ const PRETTY_PAGE = 1
33
33
  const FIRST_PAGE = 0
34
34
  const LAST_PAGE = 1
35
35
  const ONE_EDIT = 1
36
+ const WEB_URL = /^https?:\/\//i
37
+ /** Fields edit dates as `YYYY-MM-DD`. */
38
+ const DATE_LENGTH = 10
39
+ const DATE_PART_LENGTH = 2
40
+ /** Date's months count from zero, people's from one. */
41
+ const MONTH_OFFSET = 1
42
+ const INPUT_TYPES = { number: "number", date: "date", url: "url" }
36
43
 
37
44
  export const DataGrid = {
38
45
  /**
@@ -317,15 +324,14 @@ export const DataGrid = {
317
324
  })
318
325
  }
319
326
 
320
- const isNumber = column.type === "number"
327
+ const saved = row[column.id]
321
328
  return Input.render({
322
329
  size: "sm",
323
330
  ariaLabel: column.title,
324
- inputType: isNumber ? "number" : column.type === "date" ? "date" : "text",
325
- value: value == null ? "" : String(value),
331
+ inputType: INPUT_TYPES[column.type] ?? "text",
332
+ value: column.type === "date" ? toDateField(value) : (value ?? ""),
326
333
  isFullWidth: true,
327
- onChange: (text) =>
328
- change(isNumber ? (text === "" ? null : Number(text)) : text),
334
+ onChange: (text) => change(fromField(text, column.type, saved)),
329
335
  })
330
336
  },
331
337
 
@@ -337,8 +343,16 @@ export const DataGrid = {
337
343
  * @param {any} payload.value The value to render.
338
344
  * @returns {any} The value to be rendered.
339
345
  */
340
- renderValue(_props, { value }) {
341
- return value
346
+ renderValue(_props, { value, column }) {
347
+ // A link only for a web address: a `javascript:` one must not run.
348
+ return when(
349
+ column.type === "url" && WEB_URL.test(String(value ?? "")),
350
+ () =>
351
+ html`<a href=${value} target="_blank" rel="noopener noreferrer"
352
+ >${value}</a
353
+ >`,
354
+ () => value,
355
+ )
342
356
  },
343
357
 
344
358
  /**
@@ -581,3 +595,38 @@ function getVirtualizationState(props, totalItems) {
581
595
  visibleRange: { start, end },
582
596
  }
583
597
  }
598
+
599
+ /**
600
+ * A date, in whatever form a row keeps it, as a date field shows it.
601
+ * @param {Date|number|string|null|undefined} value
602
+ * @returns {string}
603
+ */
604
+ function toDateField(value) {
605
+ if (value == null || value === "") return ""
606
+ if (typeof value === "string") return value.slice(0, DATE_LENGTH)
607
+ const date = new Date(value)
608
+ if (Number.isNaN(date.getTime())) return ""
609
+ const pad = (part) => String(part).padStart(DATE_PART_LENGTH, "0")
610
+ return `${date.getFullYear()}-${pad(date.getMonth() + MONTH_OFFSET)}-${pad(date.getDate())}`
611
+ }
612
+
613
+ /**
614
+ * What a field holds, back in the form the row kept the value in: a number,
615
+ * or a date as a `Date`, a timestamp or a string, as it was. A date that had
616
+ * a time of day keeps it.
617
+ * @param {string} text
618
+ * @param {string} type The column's type.
619
+ * @param {any} saved The row's own value.
620
+ * @returns {any}
621
+ */
622
+ function fromField(text, type, saved) {
623
+ if (type === "number") return text === "" ? null : Number(text)
624
+ if (type !== "date") return text
625
+ if (text === "") return null
626
+ if (!(saved instanceof Date) && typeof saved !== "number") return text
627
+
628
+ const [year, month, day] = text.split("-").map(Number)
629
+ const date = new Date(saved)
630
+ date.setFullYear(year, month - MONTH_OFFSET, day)
631
+ return saved instanceof Date ? date : date.getTime()
632
+ }
@@ -26,7 +26,11 @@ export interface DataGridVirtualization {
26
26
  export interface Column {
27
27
  id: string
28
28
  title: string
29
- type?: "string" | "number" | "boolean" | "date"
29
+ /**
30
+ * `"url"` cells show web addresses as links. A date column's editor gives
31
+ * back dates in the form the row keeps them: `Date`, timestamp or string.
32
+ */
33
+ type?: "string" | "number" | "boolean" | "date" | "url"
30
34
  isSortable?: boolean
31
35
  isFilterable?: boolean
32
36
  filter?: {