polymorph-ui-components 0.6.1 → 0.7.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/README.md CHANGED
@@ -138,8 +138,15 @@ Each component documents its **complete** variable surface in [`docs/`](docs/).
138
138
 
139
139
  The same components compile to **framework-agnostic custom elements** — theming works identically because it's pure CSS.
140
140
 
141
+ ### From a CDN — no build step, no install
142
+
143
+ The bundle is self-contained (the Svelte runtime is compiled in) and registers every `<pui-*>` element on import, so a single `<script>` tag in a plain `.html` file is enough:
144
+
141
145
  ```html
142
- <script type="module" src="polymorph-ui-components/wc"></script>
146
+ <script
147
+ type="module"
148
+ src="https://cdn.jsdelivr.net/npm/polymorph-ui-components@latest/dist-wc/index.js"
149
+ ></script>
143
150
 
144
151
  <pui-button text="Save"></pui-button>
145
152
  <pui-input placeholder="Search…"></pui-input>
@@ -152,7 +159,24 @@ The same components compile to **framework-agnostic custom elements** — themin
152
159
  </style>
153
160
  ```
154
161
 
155
- Drop them into React, Vue, Angular, Astro, or a plain `.html` file — no build step required.
162
+ Pin an exact version for production — `…/polymorph-ui-components@<version>/dist-wc/index.js` — so a release can never change your page underneath you. [unpkg](https://unpkg.com) serves the same path if you prefer it:
163
+
164
+ ```
165
+ https://cdn.jsdelivr.net/npm/polymorph-ui-components@<version>/dist-wc/index.js
166
+ https://unpkg.com/polymorph-ui-components@<version>/dist-wc/index.js
167
+ ```
168
+
169
+ ### From npm, through a bundler
170
+
171
+ ```bash
172
+ npm install polymorph-ui-components
173
+ ```
174
+
175
+ ```js
176
+ import 'polymorph-ui-components/wc';
177
+ ```
178
+
179
+ Either way, drop them into React, Vue, Angular, Astro, or a plain `.html` file.
156
180
 
157
181
  ---
158
182
 
@@ -30,6 +30,7 @@
30
30
  headerActions,
31
31
  headerContent,
32
32
  message,
33
+ messageAttachments,
33
34
  empty,
34
35
  composerLeading,
35
36
  sendIcon,
@@ -83,7 +84,16 @@
83
84
  />
84
85
  {/if}
85
86
 
86
- <ChatMessageList {messages} {autoscroll} {message} {empty} {allowCopy} {onretry} {onfeedback} />
87
+ <ChatMessageList
88
+ {messages}
89
+ {autoscroll}
90
+ {message}
91
+ {messageAttachments}
92
+ {empty}
93
+ {allowCopy}
94
+ {onretry}
95
+ {onfeedback}
96
+ />
87
97
 
88
98
  <div class="footer">
89
99
  {#if showSuggestions}
@@ -29,6 +29,7 @@ export type OptionalChatProperties = {
29
29
  headerActions?: Snippet;
30
30
  headerContent?: Snippet;
31
31
  message?: Snippet<[ChatMessageData]>;
32
+ messageAttachments?: Snippet<[ChatMessageData]>;
32
33
  empty?: Snippet;
33
34
  composerLeading?: Snippet;
34
35
  sendIcon?: Snippet;
@@ -252,6 +252,10 @@
252
252
  margin: var(--chat-message-attachments-margin, 4px 0 0 0);
253
253
  }
254
254
 
255
+ .message-attachments:empty {
256
+ display: none;
257
+ }
258
+
255
259
  .actions {
256
260
  display: flex;
257
261
  align-items: center;
@@ -12,7 +12,7 @@ export type OptionalChatMessageProperties = {
12
12
  status?: ChatMessageStatus;
13
13
  avatar?: Snippet;
14
14
  header?: Snippet;
15
- attachments?: Snippet;
15
+ attachments?: Snippet | null;
16
16
  allowCopy?: boolean;
17
17
  actions?: Snippet;
18
18
  copyLabel?: string;
@@ -14,6 +14,7 @@
14
14
  messages,
15
15
  autoscroll = true,
16
16
  message,
17
+ messageAttachments,
17
18
  empty,
18
19
  jumpLabel = 'Jump to latest',
19
20
  jumpIcon,
@@ -105,6 +106,9 @@
105
106
  {#if typeof message === 'function'}
106
107
  {@render message(msg)}
107
108
  {:else}
109
+ {#snippet attachmentsFor()}
110
+ {@render messageAttachments?.(msg)}
111
+ {/snippet}
108
112
  <ChatMessage
109
113
  role={msg.role}
110
114
  content={msg.content}
@@ -112,6 +116,7 @@
112
116
  streaming={msg.streaming}
113
117
  status={msg.status}
114
118
  allowCopy={allowCopy && partyOf(msg.role) === 'responder'}
119
+ attachments={typeof messageAttachments === 'function' ? attachmentsFor : null}
115
120
  onretry={retryFor(msg)}
116
121
  onfeedback={feedbackFor(msg)}
117
122
  />
@@ -8,6 +8,7 @@ export type MandatoryChatMessageListProperties = {
8
8
  export type OptionalChatMessageListProperties = {
9
9
  autoscroll?: boolean;
10
10
  message?: Snippet<[ChatMessageData]>;
11
+ messageAttachments?: Snippet<[ChatMessageData]>;
11
12
  empty?: Snippet;
12
13
  jumpLabel?: string;
13
14
  jumpIcon?: Snippet;
@@ -26,6 +26,7 @@
26
26
  closeIcon,
27
27
  editIcon,
28
28
  deleteIcon,
29
+ itemFooter,
29
30
  testId,
30
31
  onimageclick,
31
32
  oneditclick,
@@ -47,6 +48,7 @@
47
48
  let hasPrevious = $derived(loop ? images.length > 1 : activeIndex > 0);
48
49
  let hasNext = $derived(loop ? images.length > 1 : activeIndex < images.length - 1);
49
50
  let itemsAreInteractive = $derived(enableLightbox || typeof onimageclick === 'function');
51
+ let hasItemFooter = $derived(view === 'grid' && typeof itemFooter === 'function');
50
52
  let showEditButton = $derived(typeof oneditclick === 'function');
51
53
  let showDeleteButton = $derived(typeof ondeleteclick === 'function');
52
54
  let showItemActions = $derived(showEditButton || showDeleteButton);
@@ -181,19 +183,32 @@
181
183
  {/if}
182
184
  {/snippet}
183
185
 
184
- {#snippet itemContent(image: GalleryImage)}
185
- <Img src={image.thumbnail ?? image.src} alt={image.alt} fallback={image.fallback} />
186
- {#if view === 'list'}
187
- <span class="list-text">
188
- <span class="list-title">{image.alt}</span>
189
- {#if typeof image.caption === 'string' && image.caption.length > 0}
190
- <span class="list-caption">{image.caption}</span>
191
- {/if}
186
+ {#snippet itemContent(image: GalleryImage, index: number)}
187
+ {#if hasItemFooter}
188
+ <span class="grid-image-wrap">
189
+ <Img src={image.thumbnail ?? image.src} alt={image.alt} fallback={image.fallback} />
192
190
  </span>
191
+ {#if typeof itemFooter === 'function'}
192
+ {@render itemFooter(image, index)}
193
+ {/if}
194
+ {:else}
195
+ <Img src={image.thumbnail ?? image.src} alt={image.alt} fallback={image.fallback} />
196
+ {#if view === 'list'}
197
+ <span class="list-text">
198
+ <span class="list-title">{image.alt}</span>
199
+ {#if typeof image.caption === 'string' && image.caption.length > 0}
200
+ <span class="list-caption">{image.caption}</span>
201
+ {/if}
202
+ </span>
203
+ {/if}
193
204
  {/if}
194
205
  {/snippet}
195
206
 
196
- <div class="gallery {view} {classes ?? ''}" data-pw={testId} role="list">
207
+ <div
208
+ class="gallery {view} {hasItemFooter ? 'has-item-footer' : ''} {classes ?? ''}"
209
+ data-pw={testId}
210
+ role="list"
211
+ >
197
212
  {#each images as image, index (index)}
198
213
  <div class="gallery-item" role="listitem">
199
214
  {#if itemsAreInteractive}
@@ -204,11 +219,11 @@
204
219
  ? `View image ${index + 1} of ${images.length}: ${image.alt}`
205
220
  : image.alt}
206
221
  >
207
- {@render itemContent(image)}
222
+ {@render itemContent(image, index)}
208
223
  </button>
209
224
  {:else}
210
225
  <div class="gallery-item-content">
211
- {@render itemContent(image)}
226
+ {@render itemContent(image, index)}
212
227
  </div>
213
228
  {/if}
214
229
  {#if showItemActions}
@@ -297,7 +312,10 @@
297
312
 
298
313
  .gallery.grid {
299
314
  display: grid;
300
- grid-template-columns: repeat(var(--gallery-columns, 3), 1fr);
315
+ grid-template-columns: var(
316
+ --gallery-grid-template-columns,
317
+ repeat(var(--gallery-columns, 3), 1fr)
318
+ );
301
319
  gap: var(--gallery-gap, 8px);
302
320
  }
303
321
 
@@ -322,6 +340,12 @@
322
340
  --image-transition: var(--gallery-item-image-transition);
323
341
  }
324
342
 
343
+ .grid.has-item-footer .gallery-item {
344
+ aspect-ratio: auto;
345
+ overflow: visible;
346
+ border-radius: 0px;
347
+ }
348
+
325
349
  .list .gallery-item {
326
350
  display: flex;
327
351
  align-items: center;
@@ -355,6 +379,25 @@
355
379
  border-radius: var(--gallery-item-border-radius, 0px);
356
380
  }
357
381
 
382
+ .grid.has-item-footer .gallery-item-content {
383
+ display: flex;
384
+ flex-direction: column;
385
+ height: auto;
386
+ border-radius: var(--gallery-item-border-radius, 0px);
387
+ overflow: hidden;
388
+ }
389
+
390
+ .grid-image-wrap {
391
+ display: block;
392
+ aspect-ratio: var(--gallery-item-aspect-ratio, 1);
393
+ overflow: hidden;
394
+ --image-width: 100%;
395
+ --image-height: 100%;
396
+ --image-object-fit: var(--gallery-item-image-fit, cover);
397
+ --image-border-radius: 0px;
398
+ --image-transition: var(--gallery-item-image-transition);
399
+ }
400
+
358
401
  .list .gallery-item-content {
359
402
  display: flex;
360
403
  align-items: center;
@@ -24,6 +24,7 @@ export type OptionalGalleryProperties = {
24
24
  closeIcon?: Snippet;
25
25
  editIcon?: Snippet;
26
26
  deleteIcon?: Snippet;
27
+ itemFooter?: Snippet<[GalleryImage, number]>;
27
28
  testId?: string;
28
29
  classes?: string;
29
30
  };
@@ -88,13 +88,13 @@
88
88
  isImage
89
89
  };
90
90
  items = [...items, item];
91
+ const updatedItem = items[items.length - 1];
91
92
 
92
93
  if (isImage) {
93
94
  const reader = new FileReader();
94
95
  reader.onload = (event) => {
95
96
  if (typeof event.target?.result === 'string') {
96
- item.src = event.target.result;
97
- items = [...items];
97
+ updatedItem.src = event.target.result;
98
98
  }
99
99
  };
100
100
  reader.readAsDataURL(file);
@@ -221,6 +221,7 @@
221
221
 
222
222
  .slot-content {
223
223
  display: var(--modal-display, flex);
224
+ flex-direction: var(--modal-flex-direction, row);
224
225
  overflow-y: var(--modal-overflow-y, scroll);
225
226
  scrollbar-width: var(--modal-scrollbar-width, none);
226
227
  }