@inglorious/ui 8.4.0 → 8.4.1

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": "8.4.0",
3
+ "version": "8.4.1",
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",
@@ -306,21 +306,21 @@
306
306
  "**/*.css"
307
307
  ],
308
308
  "peerDependencies": {
309
- "@inglorious/store": "11.3.0",
310
- "@inglorious/utils": "5.0.0",
311
- "@inglorious/web": "6.8.0"
309
+ "@inglorious/store": "12.0.0",
310
+ "@inglorious/utils": "5.1.0",
311
+ "@inglorious/web": "6.8.1"
312
312
  },
313
313
  "devDependencies": {
314
314
  "@iconify-icons/fa7-brands": "^2.0.0",
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": "11.1.5",
318
+ "@inglorious/charts": "11.1.6",
319
319
  "@inglorious/eslint-config": "1.1.4",
320
- "@inglorious/store": "11.3.0",
321
- "@inglorious/vite-plugin-jsx": "2.0.4",
322
- "@inglorious/vite-plugin-vue": "1.0.5",
323
- "@inglorious/web": "6.8.0",
320
+ "@inglorious/store": "12.0.0",
321
+ "@inglorious/vite-plugin-jsx": "2.0.5",
322
+ "@inglorious/vite-plugin-vue": "1.0.6",
323
+ "@inglorious/web": "6.8.1",
324
324
  "@storybook/addon-docs": "^10.6.0",
325
325
  "@storybook/web-components-vite": "^10.6.0",
326
326
  "happy-dom": "^20.14.0",
@@ -12,40 +12,6 @@ import { Icon } from "../../data-display/icon/index.js"
12
12
  import { fallbackLabel } from "../../shared/fallback-label.js"
13
13
  import { formatOption, getOptionValue, isOptionSelected } from "./helpers.js"
14
14
 
15
- /**
16
- * Opens the native dropdown when the arrow strip is pressed.
17
- *
18
- * The arrow is a sibling of the select, not an overlay on it, so a press on it
19
- * has no control underneath to hit. A native popup only opens from a trusted
20
- * gesture on the select itself, and the strip is outside it.
21
- *
22
- * `showPicker()` is the API for that. It needs transient user activation, which
23
- * a real click has and a dispatched event does not, and it throws rather than
24
- * silently doing nothing, so the fallback covers the browsers without it.
25
- *
26
- * Deliberately on `click`, not `pointerdown`: focusing from pointerdown gets
27
- * the focus moved away again by the browser's own mousedown handling, which
28
- * showed up as a focus immediately followed by a blur.
29
- *
30
- * @param {MouseEvent} event
31
- */
32
- function openFromArrowStrip(event) {
33
- const frame = event.currentTarget
34
- const select = frame?.querySelector("select")
35
- if (!select || select.disabled || event.target === select) return
36
-
37
- event.preventDefault()
38
- if (typeof select.showPicker === "function") {
39
- try {
40
- select.showPicker()
41
- return
42
- } catch {
43
- // No user activation, or the popup is already showing.
44
- }
45
- }
46
- select.focus()
47
- }
48
-
49
15
  /**
50
16
  * Renders a styled native select control with optional single or multi-select modes.
51
17
  * Options can be primitives or objects and are rendered with consistent theming.
@@ -208,3 +174,37 @@ export const Select = {
208
174
  </div>`
209
175
  },
210
176
  }
177
+
178
+ /**
179
+ * Opens the native dropdown when the arrow strip is pressed.
180
+ *
181
+ * The arrow is a sibling of the select, not an overlay on it, so a press on it
182
+ * has no control underneath to hit. A native popup only opens from a trusted
183
+ * gesture on the select itself, and the strip is outside it.
184
+ *
185
+ * `showPicker()` is the API for that. It needs transient user activation, which
186
+ * a real click has and a dispatched event does not, and it throws rather than
187
+ * silently doing nothing, so the fallback covers the browsers without it.
188
+ *
189
+ * Deliberately on `click`, not `pointerdown`: focusing from pointerdown gets
190
+ * the focus moved away again by the browser's own mousedown handling, which
191
+ * showed up as a focus immediately followed by a blur.
192
+ *
193
+ * @param {MouseEvent} event
194
+ */
195
+ function openFromArrowStrip(event) {
196
+ const frame = event.currentTarget
197
+ const select = frame?.querySelector("select")
198
+ if (!select || select.disabled || event.target === select) return
199
+
200
+ event.preventDefault()
201
+ if (typeof select.showPicker === "function") {
202
+ try {
203
+ select.showPicker()
204
+ return
205
+ } catch {
206
+ // No user activation, or the popup is already showing.
207
+ }
208
+ }
209
+ select.focus()
210
+ }
@@ -8,14 +8,6 @@ import { Carousel } from "./index.js"
8
8
 
9
9
  const COLORS = ["#22d3ee", "#4f46e5", "#f59e0b", "#10b981", "#ef4444"]
10
10
 
11
- const slide = (index) =>
12
- html`<div
13
- style="display:grid;place-items:center;height:14rem;border-radius:0.5rem;
14
- background:${COLORS[index]};color:#fff;font:700 2rem sans-serif;"
15
- >
16
- ${index + 1}
17
- </div>`
18
-
19
11
  const items = COLORS.map((color, index) => slide(index))
20
12
 
21
13
  export default {
@@ -164,3 +156,12 @@ ManyPerView.args = {
164
156
  ManyPerView.decorators = [
165
157
  (story) => html`<div style="--iw-carousel-item-size: 14rem">${story()}</div>`,
166
158
  ]
159
+
160
+ function slide(index) {
161
+ return html`<div
162
+ style="display:grid;place-items:center;height:14rem;border-radius:0.5rem;
163
+ background:${COLORS[index]};color:#fff;font:700 2rem sans-serif;"
164
+ >
165
+ ${index + 1}
166
+ </div>`
167
+ }
@@ -44,16 +44,6 @@ const baseColumns = [
44
44
  },
45
45
  ]
46
46
 
47
- function createRows(count) {
48
- return Array.from({ length: count }, (_, index) => ({
49
- id: index + 1,
50
- name: `User ${index + 1}`,
51
- age: 20 + (index % 35),
52
- active: index % 3 !== 0,
53
- role: index % 4 === 0 ? "Admin" : "User",
54
- }))
55
- }
56
-
57
47
  export default {
58
48
  title: "Data Display/DataGrid",
59
49
  tags: ["autodocs"],
@@ -156,3 +146,13 @@ export const Virtualized = {
156
146
  },
157
147
  },
158
148
  }
149
+
150
+ function createRows(count) {
151
+ return Array.from({ length: count }, (_, index) => ({
152
+ id: index + 1,
153
+ name: `User ${index + 1}`,
154
+ age: 20 + (index % 35),
155
+ active: index % 3 !== 0,
156
+ role: index % 4 === 0 ? "Admin" : "User",
157
+ }))
158
+ }
@@ -15,27 +15,6 @@ const PRETTY_INDEX = 1
15
15
  // measuring is an update, so measuring on each render would loop forever.
16
16
  const measured = new WeakSet()
17
17
 
18
- /**
19
- * Notifies `mount` once per list element, after a frame and once it is in the
20
- * document: before that it has no layout to measure.
21
- * @param {HTMLElement} el
22
- * @param {VirtualListEntity} entity
23
- * @param {Api} api
24
- */
25
- function measureOnce(el, entity, api) {
26
- if (measured.has(el)) return
27
- measured.add(el)
28
-
29
- const measure = () => {
30
- if (!el.isConnected) {
31
- requestAnimationFrame(measure)
32
- return
33
- }
34
- api.notify(`#${entity.id}:mount`, el)
35
- }
36
- requestAnimationFrame(measure)
37
- }
38
-
39
18
  /**
40
19
  * Renders a virtualized list that only mounts visible items for performance.
41
20
  * Uses item height estimates to compute scroll ranges and notifies on mount/scroll.
@@ -125,3 +104,24 @@ export function renderItem(entity, { item, index }, api) {
125
104
  ${index + PRETTY_INDEX}. ${JSON.stringify(item)}
126
105
  </div>`
127
106
  }
107
+
108
+ /**
109
+ * Notifies `mount` once per list element, after a frame and once it is in the
110
+ * document: before that it has no layout to measure.
111
+ * @param {HTMLElement} el
112
+ * @param {VirtualListEntity} entity
113
+ * @param {Api} api
114
+ */
115
+ function measureOnce(el, entity, api) {
116
+ if (measured.has(el)) return
117
+ measured.add(el)
118
+
119
+ const measure = () => {
120
+ if (!el.isConnected) {
121
+ requestAnimationFrame(measure)
122
+ return
123
+ }
124
+ api.notify(`#${entity.id}:mount`, el)
125
+ }
126
+ requestAnimationFrame(measure)
127
+ }
@@ -1,10 +1,22 @@
1
+ export const beforeAfterImages = {
2
+ before: {
3
+ src: svg("BEFORE", "#9ca3af", "#374151"),
4
+ alt: "Before — grayscale",
5
+ },
6
+ after: {
7
+ src: svg("AFTER", "#22d3ee", "#4f46e5"),
8
+ alt: "After — color graded",
9
+ },
10
+ }
11
+
1
12
  // Self-contained demo images for the Before/After control, so the dashboard
2
13
  // renders the comparison without any network requests.
3
14
 
4
- const svg = (label, from, to) =>
5
- "data:image/svg+xml," +
6
- encodeURIComponent(
7
- `<svg xmlns="http://www.w3.org/2000/svg" width="960" height="540" viewBox="0 0 960 540">
15
+ function svg(label, from, to) {
16
+ return (
17
+ "data:image/svg+xml," +
18
+ encodeURIComponent(
19
+ `<svg xmlns="http://www.w3.org/2000/svg" width="960" height="540" viewBox="0 0 960 540">
8
20
  <defs>
9
21
  <linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
10
22
  <stop offset="0" stop-color="${from}"/>
@@ -15,15 +27,6 @@ const svg = (label, from, to) =>
15
27
  <text x="48" y="496" font-family="sans-serif" font-size="72" font-weight="700"
16
28
  fill="rgba(255,255,255,0.92)">${label}</text>
17
29
  </svg>`,
30
+ )
18
31
  )
19
-
20
- export const beforeAfterImages = {
21
- before: {
22
- src: svg("BEFORE", "#9ca3af", "#374151"),
23
- alt: "Before — grayscale",
24
- },
25
- after: {
26
- src: svg("AFTER", "#22d3ee", "#4f46e5"),
27
- alt: "After — color graded",
28
- },
29
32
  }
@@ -114,38 +114,8 @@ const categoryMeta = {
114
114
  },
115
115
  }
116
116
 
117
- const previewRow = (children) =>
118
- html`<div class="iw-primitive-preview-row">${children}</div>`
119
-
120
- const previewColumn = (children) =>
121
- html`<div class="iw-primitive-preview-column">${children}</div>`
122
-
123
117
  const livePreviews = new Map()
124
118
 
125
- /**
126
- * Entity-driven primitives render their controls from state, so a preview
127
- * without a store freezes: a carousel arrow would recompute from the same page
128
- * forever, and a dragged divider would never move. Give each one its own store,
129
- * created once and reused on every re-render so the preview keeps its state.
130
- * @param {Object.<string, any>} types
131
- * @param {Object.<string, any>} entity
132
- * @returns {HTMLElement}
133
- */
134
- function livePreview(types, entity) {
135
- if (!livePreviews.has(entity.id)) {
136
- const container = document.createElement("div")
137
- // The mount needs an element, but it must not become a box of its own:
138
- // as a flex item it would shrink and take the primitive down with it.
139
- container.style.display = "contents"
140
-
141
- const store = createStore({ types, entities: { [entity.id]: entity } })
142
- mount(store, (api) => api.render(entity.id), container)
143
- livePreviews.set(entity.id, container)
144
- }
145
-
146
- return livePreviews.get(entity.id)
147
- }
148
-
149
119
  const carouselSlides = [
150
120
  "#22d3ee",
151
121
  "#4f46e5",
@@ -1542,3 +1512,35 @@ function toTitleCase(slug) {
1542
1512
  .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
1543
1513
  .join(" ")
1544
1514
  }
1515
+
1516
+ /**
1517
+ * Entity-driven primitives render their controls from state, so a preview
1518
+ * without a store freezes: a carousel arrow would recompute from the same page
1519
+ * forever, and a dragged divider would never move. Give each one its own store,
1520
+ * created once and reused on every re-render so the preview keeps its state.
1521
+ * @param {Object.<string, any>} types
1522
+ * @param {Object.<string, any>} entity
1523
+ * @returns {HTMLElement}
1524
+ */
1525
+ function livePreview(types, entity) {
1526
+ if (!livePreviews.has(entity.id)) {
1527
+ const container = document.createElement("div")
1528
+ // The mount needs an element, but it must not become a box of its own:
1529
+ // as a flex item it would shrink and take the primitive down with it.
1530
+ container.style.display = "contents"
1531
+
1532
+ const store = createStore({ types, entities: { [entity.id]: entity } })
1533
+ mount(store, (api) => api.render(entity.id), container)
1534
+ livePreviews.set(entity.id, container)
1535
+ }
1536
+
1537
+ return livePreviews.get(entity.id)
1538
+ }
1539
+
1540
+ function previewRow(children) {
1541
+ return html`<div class="iw-primitive-preview-row">${children}</div>`
1542
+ }
1543
+
1544
+ function previewColumn(children) {
1545
+ return html`<div class="iw-primitive-preview-column">${children}</div>`
1546
+ }
@@ -61,6 +61,11 @@ export function nextMode(mode) {
61
61
  return MODE_CYCLE[(index + 1) % MODE_CYCLE.length]
62
62
  }
63
63
 
64
+ export const DEFAULTS = {
65
+ theme: DEFAULT_THEME,
66
+ mode: DEFAULT_MODE,
67
+ }
68
+
64
69
  function resolveMode(mode) {
65
70
  if (mode !== "auto") return mode
66
71
  return getSystemMode()
@@ -88,8 +93,3 @@ function ensureSystemListener() {
88
93
  systemMedia.addListener(systemListener)
89
94
  }
90
95
  }
91
-
92
- export const DEFAULTS = {
93
- theme: DEFAULT_THEME,
94
- mode: DEFAULT_MODE,
95
- }
@@ -6,10 +6,6 @@
6
6
  import { html } from "@inglorious/web"
7
7
  import { classMap } from "@inglorious/web/directives/class-map"
8
8
 
9
- function clamp(value) {
10
- return Math.max(0, Math.min(100, value))
11
- }
12
-
13
9
  /**
14
10
  * Renders a linear or circular progress indicator.
15
11
  * Indeterminate mode is used when no explicit value is provided.
@@ -90,3 +86,7 @@ export function render(props) {
90
86
  </div>
91
87
  </div>`
92
88
  }
89
+
90
+ function clamp(value) {
91
+ return Math.max(0, Math.min(100, value))
92
+ }
@@ -1,5 +1,32 @@
1
1
  const previousKeysByElement = new WeakMap()
2
2
 
3
+ /**
4
+ * Apply arbitrary properties/attributes to an element.
5
+ * Useful when entities include extra DOM fields beyond the typed API.
6
+ *
7
+ * @param {Element | null} element
8
+ * @param {Record<string, unknown>} props
9
+ */
10
+ export function applyElementProps(element, props) {
11
+ if (!element) return
12
+
13
+ const previousKeys = previousKeysByElement.get(element) ?? new Set()
14
+ const nextKeys = new Set(Object.keys(props))
15
+
16
+ for (const key of previousKeys) {
17
+ if (!nextKeys.has(key)) {
18
+ clearProp(element, key)
19
+ }
20
+ }
21
+
22
+ for (const [key, value] of Object.entries(props)) {
23
+ if (key === "class" || key === "className") continue
24
+ setProp(element, key, value)
25
+ }
26
+
27
+ previousKeysByElement.set(element, nextKeys)
28
+ }
29
+
3
30
  function clearProp(element, key) {
4
31
  if (key in element && !key.includes("-")) {
5
32
  try {
@@ -50,30 +77,3 @@ function setProp(element, key, value) {
50
77
 
51
78
  element.setAttribute(key, String(value))
52
79
  }
53
-
54
- /**
55
- * Apply arbitrary properties/attributes to an element.
56
- * Useful when entities include extra DOM fields beyond the typed API.
57
- *
58
- * @param {Element | null} element
59
- * @param {Record<string, unknown>} props
60
- */
61
- export function applyElementProps(element, props) {
62
- if (!element) return
63
-
64
- const previousKeys = previousKeysByElement.get(element) ?? new Set()
65
- const nextKeys = new Set(Object.keys(props))
66
-
67
- for (const key of previousKeys) {
68
- if (!nextKeys.has(key)) {
69
- clearProp(element, key)
70
- }
71
- }
72
-
73
- for (const [key, value] of Object.entries(props)) {
74
- if (key === "class" || key === "className") continue
75
- setProp(element, key, value)
76
- }
77
-
78
- previousKeysByElement.set(element, nextKeys)
79
- }