@jamwidgets/astro 0.6.4 → 0.6.7

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
@@ -234,6 +234,7 @@ import Subscribe from "@jamwidgets/astro/Subscribe";
234
234
  - `endpoint` - Base URL (default: `https://jamwidgets.com`)
235
235
  - `buttonText` - Submit button text (default: `'Subscribe'`)
236
236
  - `placeholder` - Email input placeholder
237
+ - `label` - Accessible name for the email input (default: `'Email address'`)
237
238
  - `successMessage` - Custom success message
238
239
  - `theme` - `'light'` | `'dark'` | `'auto'` (default: `'light'`)
239
240
  - `class` - Additional CSS class
@@ -256,6 +257,10 @@ import SubscribeForm from "@jamwidgets/astro/SubscribeForm";
256
257
  </SubscribeForm>
257
258
  ```
258
259
 
260
+ ### Missing content
261
+
262
+ `Poll`, `Embed` polls, `Gallery` and `RandomPhoto` render nothing when their slug doesn't exist or isn't published, and log a `console.warn` naming it. Create or publish the content in the Jamwidgets dashboard and it appears on the next page load. Other failures still show a short error.
263
+
259
264
  ## JavaScript API
260
265
 
261
266
  For advanced use cases, use the JavaScript API directly:
@@ -317,6 +322,10 @@ Set semantic CSS custom properties on a wrapper or the widget itself to match yo
317
322
 
318
323
  Status colors can be customized with `--jamwidgets-color-success`, `--jamwidgets-color-error`, and `--jamwidgets-color-notice`. Components adapt to their container width, so subscribe, waitlist, comments, and feedback controls stack when embedded in a narrow column.
319
324
 
325
+ ### Cascade layers and Tailwind
326
+
327
+ Widget styles live in a `jamwidgets` cascade layer. Every component declares the order `@layer theme, base, components, utilities, jamwidgets;` before its rules, so the widgets rank above Tailwind v4's layers (including Preflight in `base`) whichever stylesheet the browser sees first. Your unlayered CSS still overrides the widgets. If your site declares its own layer order, keep `jamwidgets` after `base` or Preflight will reset the widgets' padding, borders and buttons.
328
+
320
329
  ## License
321
330
 
322
331
  MIT
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
  export { DEFAULT_ENDPOINT, API_PATH, VISITOR_STORAGE_KEY, type JamwidgetsConfig, type SeriphConfig, // deprecated alias
8
8
  type Comment, type ReactionCounts, type FormSubmitResponse, type SubscribeResponse, type JamwidgetsPost, type JamwidgetsGallery, type GalleryPhoto, type GallerySummary, type SeriphPost, // deprecated alias
9
- type Announcement, type AnnouncementType, type Poll, type PollOption, type PollSettings, type PollWithResults, type ShowResultsMode, type FeedbackType, buildUrl, getSiteKey, getVisitorId, setVisitorId, type SubmitFormOptions, submitForm, type FetchCommentsOptions, fetchComments, type PostCommentOptions, postComment, type FetchReactionsOptions, type FetchReactionsResponse, fetchReactions, type AddReactionOptions, addReaction, type RemoveReactionOptions, removeReaction, type SubscribeOptions, subscribe, type JoinWaitlistOptions, type JoinWaitlistResponse, joinWaitlist, type ViewCountsOptions, type ViewCounts, type RecordViewResponse, getViewCounts, recordView, type SubmitFeedbackOptions, type SubmitFeedbackResponse, submitFeedback, type FetchPollOptions, fetchPoll, type VotePollOptions, type VotePollResponse, votePoll, type FetchAnnouncementsOptions, fetchAnnouncements, type DismissAnnouncementOptions, dismissAnnouncement, type FetchPostsOptions, fetchPosts, type FetchPostOptions, fetchPost, type FetchGalleriesOptions, fetchGalleries, type FetchGalleryOptions, fetchGallery, type FetchRandomPhotoOptions, fetchRandomPhoto, type SiteConfig, fetchSiteConfig, POWERED_BY_URL, POWERED_BY_LABEL, type ControllerStatus, type ControllerListener, type SubscribeState, type FormState, type ReactionsState, type CommentsState, type WaitlistState, type ViewCountsState, type FeedbackState, type PollState, type AnnouncementsState, SubscribeController, WaitlistController, FormController, ReactionsController, CommentsController, ViewCountsController, FeedbackController, PollController, AnnouncementsController, } from "@jamwidgets/core";
9
+ type Announcement, type AnnouncementType, type Poll, type PollOption, type PollSettings, type PollWithResults, type ShowResultsMode, type FeedbackType, buildUrl, getSiteKey, getVisitorId, setVisitorId, type SubmitFormOptions, submitForm, type FetchCommentsOptions, fetchComments, type PostCommentOptions, postComment, type FetchReactionsOptions, type FetchReactionsResponse, fetchReactions, type AddReactionOptions, addReaction, type RemoveReactionOptions, removeReaction, type SubscribeOptions, subscribe, type JoinWaitlistOptions, type JoinWaitlistResponse, joinWaitlist, type ViewCountsOptions, type ViewCounts, type RecordViewResponse, getViewCounts, recordView, type SubmitFeedbackOptions, type SubmitFeedbackResponse, submitFeedback, type FetchPollOptions, fetchPoll, type VotePollOptions, type VotePollResponse, votePoll, type FetchAnnouncementsOptions, fetchAnnouncements, type DismissAnnouncementOptions, dismissAnnouncement, type FetchPostsOptions, fetchPosts, type FetchPostOptions, fetchPost, type FetchGalleriesOptions, fetchGalleries, type FetchGalleryOptions, fetchGallery, type FetchRandomPhotoOptions, fetchRandomPhoto, JamwidgetsApiError, apiError, isNotFound, type SiteConfig, fetchSiteConfig, POWERED_BY_URL, POWERED_BY_LABEL, type ControllerStatus, type ControllerListener, type SubscribeState, type FormState, type ReactionsState, type CommentsState, type WaitlistState, type ViewCountsState, type FeedbackState, type PollState, type AnnouncementsState, SubscribeController, WaitlistController, FormController, ReactionsController, CommentsController, ViewCountsController, FeedbackController, PollController, AnnouncementsController, } from "@jamwidgets/core";
10
10
  export { jamwidgetsPostsLoader, seriphPostsLoader, // deprecated alias
11
11
  type JamwidgetsPostsLoaderOptions, type SeriphPostsLoaderOptions, // deprecated alias
12
12
  jamwidgetsGalleriesLoader, type JamwidgetsGalleriesLoaderOptions, } from "./loader.js";
package/dist/index.js CHANGED
@@ -9,7 +9,9 @@ export {
9
9
  // Constants
10
10
  DEFAULT_ENDPOINT, API_PATH, VISITOR_STORAGE_KEY,
11
11
  // Helpers
12
- buildUrl, getSiteKey, getVisitorId, setVisitorId, submitForm, fetchComments, postComment, fetchReactions, addReaction, removeReaction, subscribe, joinWaitlist, getViewCounts, recordView, submitFeedback, fetchPoll, votePoll, fetchAnnouncements, dismissAnnouncement, fetchPosts, fetchPost, fetchGalleries, fetchGallery, fetchRandomPhoto, fetchSiteConfig, POWERED_BY_URL, POWERED_BY_LABEL, SubscribeController, WaitlistController, FormController, ReactionsController, CommentsController, ViewCountsController, FeedbackController, PollController, AnnouncementsController, } from "@jamwidgets/core";
12
+ buildUrl, getSiteKey, getVisitorId, setVisitorId, submitForm, fetchComments, postComment, fetchReactions, addReaction, removeReaction, subscribe, joinWaitlist, getViewCounts, recordView, submitFeedback, fetchPoll, votePoll, fetchAnnouncements, dismissAnnouncement, fetchPosts, fetchPost, fetchGalleries, fetchGallery, fetchRandomPhoto,
13
+ // Errors
14
+ JamwidgetsApiError, apiError, isNotFound, fetchSiteConfig, POWERED_BY_URL, POWERED_BY_LABEL, SubscribeController, WaitlistController, FormController, ReactionsController, CommentsController, ViewCountsController, FeedbackController, PollController, AnnouncementsController, } from "@jamwidgets/core";
13
15
  // Re-export loader (Astro-specific)
14
16
  export { jamwidgetsPostsLoader, seriphPostsLoader, // deprecated alias
15
17
  jamwidgetsGalleriesLoader, } from "./loader.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jamwidgets/astro",
3
- "version": "0.6.4",
3
+ "version": "0.6.7",
4
4
  "description": "Astro components and content loaders for Jamwidgets",
5
5
  "repository": {
6
6
  "type": "git",
@@ -60,7 +60,7 @@
60
60
  ],
61
61
  "license": "MIT",
62
62
  "dependencies": {
63
- "@jamwidgets/core": "^0.5.3"
63
+ "@jamwidgets/core": "^0.7.0"
64
64
  },
65
65
  "peerDependencies": {
66
66
  "astro": "^5.9.0 || ^6.0.0 || ^7.0.0"
@@ -151,6 +151,7 @@ import Theme from "./Theme.astro";
151
151
  </script>
152
152
 
153
153
  <style is:global>
154
+ @layer theme, base, components, utilities, jamwidgets;
154
155
  @layer jamwidgets {
155
156
  .jamwidgets-announcements {
156
157
  display: flex;
@@ -262,6 +262,7 @@ import Theme from "./Theme.astro";
262
262
  </script>
263
263
 
264
264
  <style is:global>
265
+ @layer theme, base, components, utilities, jamwidgets;
265
266
  @layer jamwidgets {
266
267
  .jamwidgets-comments {
267
268
  color: var(--jamwidgets-text-color);
package/src/Embed.astro CHANGED
@@ -64,7 +64,7 @@ import Theme from "./Theme.astro";
64
64
  userVotes?: string[];
65
65
  }
66
66
 
67
- import { getVisitorId } from "@jamwidgets/core";
67
+ import { apiError, getVisitorId, warnMissingContent } from "@jamwidgets/core";
68
68
 
69
69
  class JamwidgetsEmbed extends HTMLElement {
70
70
  connectedCallback() {
@@ -134,8 +134,14 @@ import Theme from "./Theme.astro";
134
134
 
135
135
  async function loadPoll() {
136
136
  try {
137
- const response = await fetch(`${config.endpoint}/polls/${config.slug}`, { headers });
138
- if (!response.ok) throw new Error("Failed to load poll");
137
+ const response = await fetch(`${config.endpoint}/polls/${encodeURIComponent(config.slug)}`, { headers });
138
+ if (response.status === 404) {
139
+ // Missing or unpublished poll: show readers nothing.
140
+ container.style.display = "none";
141
+ warnMissingContent("Embedded poll", config.slug);
142
+ return;
143
+ }
144
+ if (!response.ok) throw await apiError(response, "Failed to load poll");
139
145
  const data = await response.json();
140
146
  poll = data.poll_with_results;
141
147
  renderPoll();
@@ -271,8 +277,7 @@ import Theme from "./Theme.astro";
271
277
  });
272
278
 
273
279
  if (!response.ok) {
274
- const error = await response.json();
275
- throw new Error(error.message || "Failed to vote");
280
+ throw await apiError(response, "Failed to vote");
276
281
  }
277
282
 
278
283
  const data = await response.json();
@@ -312,6 +317,7 @@ import Theme from "./Theme.astro";
312
317
  </script>
313
318
 
314
319
  <style is:global>
320
+ @layer theme, base, components, utilities, jamwidgets;
315
321
  @layer jamwidgets {
316
322
  .jamwidgets-poll {
317
323
  font-family: inherit;
@@ -3,7 +3,7 @@
3
3
  * Jamwidgets Feedback Component
4
4
  *
5
5
  * Displays a feedback form for collecting user suggestions.
6
- * Supports different feedback types (bug, feature, improvement, other).
6
+ * Supports the API's feedback types: feature, bug and general.
7
7
  *
8
8
  * @example
9
9
  * <Feedback siteKey={import.meta.env.JAMWIDGETS_SITE_KEY} />
@@ -62,11 +62,7 @@ import Theme from "./Theme.astro";
62
62
  <span>Bug</span>
63
63
  </label>
64
64
  <label class="jamwidgets-feedback-type">
65
- <input type="radio" name="feedbackType" value="improvement" />
66
- <span>Improvement</span>
67
- </label>
68
- <label class="jamwidgets-feedback-type">
69
- <input type="radio" name="feedbackType" value="other" />
65
+ <input type="radio" name="feedbackType" value="general" />
70
66
  <span>Other</span>
71
67
  </label>
72
68
  </div>
@@ -90,7 +86,7 @@ import Theme from "./Theme.astro";
90
86
  </div>
91
87
 
92
88
  <script>
93
- import { getVisitorId } from "@jamwidgets/core";
89
+ import { apiError, getVisitorId } from "@jamwidgets/core";
94
90
 
95
91
  document.querySelectorAll("[data-jamwidgets-feedback]").forEach((container) => {
96
92
  const endpoint = (container as HTMLElement).dataset.endpoint;
@@ -117,7 +113,7 @@ import Theme from "./Theme.astro";
117
113
  if (!content) return;
118
114
 
119
115
  const typeInput = form.querySelector('input[name="feedbackType"]:checked') as HTMLInputElement | null;
120
- const feedbackType = typeInput?.value || "other";
116
+ const feedbackType = typeInput?.value || "general";
121
117
 
122
118
  submitBtn.disabled = true;
123
119
  submitBtn.textContent = "Sending...";
@@ -132,8 +128,7 @@ import Theme from "./Theme.astro";
132
128
  });
133
129
 
134
130
  if (!response.ok) {
135
- const error = await response.json();
136
- throw new Error(error.message || "Failed to submit feedback");
131
+ throw await apiError(response, "Failed to submit feedback");
137
132
  }
138
133
 
139
134
  textarea.value = "";
@@ -163,6 +158,7 @@ import Theme from "./Theme.astro";
163
158
  </script>
164
159
 
165
160
  <style is:global>
161
+ @layer theme, base, components, utilities, jamwidgets;
166
162
  @layer jamwidgets {
167
163
  .jamwidgets-feedback {
168
164
  font-family: inherit;
package/src/Form.astro CHANGED
@@ -67,7 +67,7 @@ import Theme from "./Theme.astro";
67
67
  </form>
68
68
 
69
69
  <script>
70
- import { generateUUID, getAttributionHeaders, getVisitorId } from "@jamwidgets/core";
70
+ import { apiError, generateUUID, getAttributionHeaders, getVisitorId } from "@jamwidgets/core";
71
71
 
72
72
  interface FormSubmitResponse {
73
73
  success: boolean;
@@ -145,8 +145,7 @@ import Theme from "./Theme.astro";
145
145
  });
146
146
 
147
147
  if (!response.ok) {
148
- const errorText = await response.text();
149
- throw new Error(errorText || "Submission failed");
148
+ throw await apiError(response, "Submission failed");
150
149
  }
151
150
 
152
151
  const result: FormSubmitResponse = await response.json();
@@ -193,6 +192,7 @@ import Theme from "./Theme.astro";
193
192
  </script>
194
193
 
195
194
  <style is:global>
195
+ @layer theme, base, components, utilities, jamwidgets;
196
196
  @layer jamwidgets {
197
197
  .jamwidgets-form-message {
198
198
  margin-top: 1rem;
package/src/Poll.astro CHANGED
@@ -89,7 +89,7 @@ import Theme from "./Theme.astro";
89
89
  userVotes?: string[];
90
90
  }
91
91
 
92
- import { getVisitorId } from "@jamwidgets/core";
92
+ import { apiError, getVisitorId, warnMissingContent } from "@jamwidgets/core";
93
93
 
94
94
  document.querySelectorAll("[data-jamwidgets-poll]").forEach((container) => {
95
95
  const endpoint = (container as HTMLElement).dataset.endpoint;
@@ -118,8 +118,14 @@ import Theme from "./Theme.astro";
118
118
 
119
119
  async function loadPoll() {
120
120
  try {
121
- const response = await fetch(`${endpoint}/polls/${pollSlug}`, { headers });
122
- if (!response.ok) throw new Error("Failed to load poll");
121
+ const response = await fetch(`${endpoint}/polls/${encodeURIComponent(pollSlug!)}`, { headers });
122
+ if (response.status === 404) {
123
+ // Missing or unpublished poll: show visitors nothing.
124
+ (container as HTMLElement).style.display = "none";
125
+ warnMissingContent("Poll", pollSlug!);
126
+ return;
127
+ }
128
+ if (!response.ok) throw await apiError(response, "Failed to load poll");
123
129
  const data = await response.json();
124
130
  poll = data.poll_with_results;
125
131
  renderPoll();
@@ -255,8 +261,7 @@ import Theme from "./Theme.astro";
255
261
  });
256
262
 
257
263
  if (!response.ok) {
258
- const error = await response.json();
259
- throw new Error(error.message || "Failed to vote");
264
+ throw await apiError(response, "Failed to vote");
260
265
  }
261
266
 
262
267
  const data = await response.json();
@@ -293,6 +298,7 @@ import Theme from "./Theme.astro";
293
298
  </script>
294
299
 
295
300
  <style is:global>
301
+ @layer theme, base, components, utilities, jamwidgets;
296
302
  @layer jamwidgets {
297
303
  .jamwidgets-poll {
298
304
  font-family: inherit;
@@ -221,6 +221,7 @@ import Theme from "./Theme.astro";
221
221
  </script>
222
222
 
223
223
  <style is:global>
224
+ @layer theme, base, components, utilities, jamwidgets;
224
225
  @layer jamwidgets {
225
226
  .jamwidgets-reactions {
226
227
  display: flex;
@@ -26,6 +26,8 @@ interface Props {
26
26
  buttonText?: string;
27
27
  /** Email input placeholder (default: 'your@email.com') */
28
28
  placeholder?: string;
29
+ /** Accessible name for the email input (default: 'Email address') */
30
+ label?: string;
29
31
  /** Success message override (default: uses server response) */
30
32
  successMessage?: string;
31
33
  /** Theme preset. Defaults to inheriting the host page. */
@@ -42,6 +44,7 @@ const {
42
44
  endpoint = DEFAULT_ENDPOINT,
43
45
  buttonText = "Subscribe",
44
46
  placeholder = "your@email.com",
47
+ label = "Email address",
45
48
  successMessage,
46
49
  theme = "inherit",
47
50
  class: className = "",
@@ -68,6 +71,7 @@ import Theme from "./Theme.astro";
68
71
  name="email"
69
72
  required
70
73
  placeholder={placeholder}
74
+ aria-label={label}
71
75
  class="jamwidgets-subscribe-input"
72
76
  autocomplete="email"
73
77
  />
@@ -93,7 +97,7 @@ import Theme from "./Theme.astro";
93
97
  message: string;
94
98
  }
95
99
 
96
- import { getVisitorId } from "@jamwidgets/core";
100
+ import { apiError, getVisitorId } from "@jamwidgets/core";
97
101
 
98
102
  document.querySelectorAll("[data-jamwidgets-subscribe]").forEach((form) => {
99
103
  form.addEventListener("submit", async (e) => {
@@ -141,8 +145,7 @@ import Theme from "./Theme.astro";
141
145
  });
142
146
 
143
147
  if (!response.ok) {
144
- const errorText = await response.text();
145
- throw new Error(errorText || "Subscription failed");
148
+ throw await apiError(response, "Subscription failed");
146
149
  }
147
150
 
148
151
  const result: SubscribeResponse = await response.json();
@@ -190,6 +193,7 @@ import Theme from "./Theme.astro";
190
193
  </script>
191
194
 
192
195
  <style is:global>
196
+ @layer theme, base, components, utilities, jamwidgets;
193
197
  @layer jamwidgets {
194
198
  .jamwidgets-subscribe {
195
199
  position: relative;
@@ -71,7 +71,7 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
71
71
  </form>
72
72
 
73
73
  <script>
74
- import { getAttributionHeaders, getVisitorId } from "@jamwidgets/core";
74
+ import { apiError, getAttributionHeaders, getVisitorId } from "@jamwidgets/core";
75
75
 
76
76
  interface SubscribeResponse {
77
77
  success: boolean;
@@ -114,8 +114,7 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
114
114
  });
115
115
 
116
116
  if (!response.ok) {
117
- const errorText = await response.text();
118
- throw new Error(errorText || "Subscription failed");
117
+ throw await apiError(response, "Subscription failed");
119
118
  }
120
119
 
121
120
  const result: SubscribeResponse = await response.json();
package/src/Theme.astro CHANGED
@@ -1,4 +1,11 @@
1
1
  <style is:global>
2
+ /* Whichever stylesheet names a layer first fixes its order. If these widget
3
+ styles load before a Tailwind site's CSS, `jamwidgets` would otherwise
4
+ become the lowest layer and Tailwind's base reset would strip widget
5
+ input borders. Naming Tailwind's layers first, and `jamwidgets` last,
6
+ gives the same order whichever CSS loads first. Every widget repeats
7
+ this statement; unlayered site CSS still wins. */
8
+ @layer theme, base, components, utilities, jamwidgets;
2
9
  @layer jamwidgets {
3
10
  :where(
4
11
  .jamwidgets-announcements,
package/src/Views.astro CHANGED
@@ -149,6 +149,7 @@ import Theme from "./Theme.astro";
149
149
  </script>
150
150
 
151
151
  <style is:global>
152
+ @layer theme, base, components, utilities, jamwidgets;
152
153
  @layer jamwidgets {
153
154
  .jamwidgets-views {
154
155
  display: inline-flex;
@@ -17,6 +17,8 @@ interface Props {
17
17
  theme?: "inherit" | "light" | "dark" | "auto";
18
18
  /** Placeholder text for the email input (default: 'Enter your email') */
19
19
  placeholder?: string;
20
+ /** Accessible name for the email input (default: 'Email address') */
21
+ label?: string;
20
22
  /** Button text (default: 'Join Waitlist') */
21
23
  buttonText?: string;
22
24
  /** Success message (default: "You're on the list!") */
@@ -33,6 +35,7 @@ const {
33
35
  endpoint = DEFAULT_ENDPOINT,
34
36
  theme = "inherit",
35
37
  placeholder = "Enter your email",
38
+ label = "Email address",
36
39
  buttonText = "Join Waitlist",
37
40
  successMessage = "You're on the list!",
38
41
  class: className = "",
@@ -59,6 +62,8 @@ import Theme from "./Theme.astro";
59
62
  name="email"
60
63
  class="jamwidgets-waitlist-input"
61
64
  placeholder={placeholder}
65
+ aria-label={label}
66
+ autocomplete="email"
62
67
  required
63
68
  />
64
69
  <button type="submit" class="jamwidgets-waitlist-submit">
@@ -71,7 +76,7 @@ import Theme from "./Theme.astro";
71
76
  </div>
72
77
 
73
78
  <script>
74
- import { getVisitorId } from "@jamwidgets/core";
79
+ import { apiError, getVisitorId } from "@jamwidgets/core";
75
80
 
76
81
  document.querySelectorAll("[data-jamwidgets-waitlist]").forEach((container) => {
77
82
  const endpoint = (container as HTMLElement).dataset.endpoint;
@@ -111,8 +116,7 @@ import Theme from "./Theme.astro";
111
116
  });
112
117
 
113
118
  if (!response.ok) {
114
- const error = await response.json();
115
- throw new Error(error.message || "Failed to join waitlist");
119
+ throw await apiError(response, "Failed to join waitlist");
116
120
  }
117
121
 
118
122
  // Hide form and show success
@@ -143,6 +147,7 @@ import Theme from "./Theme.astro";
143
147
  </script>
144
148
 
145
149
  <style is:global>
150
+ @layer theme, base, components, utilities, jamwidgets;
146
151
  @layer jamwidgets {
147
152
  .jamwidgets-waitlist {
148
153
  font-family: inherit;
package/src/index.ts CHANGED
@@ -104,6 +104,11 @@ export {
104
104
  type FetchRandomPhotoOptions,
105
105
  fetchRandomPhoto,
106
106
 
107
+ // Errors
108
+ JamwidgetsApiError,
109
+ apiError,
110
+ isNotFound,
111
+
107
112
  // Site config + powered-by attribution
108
113
  type SiteConfig,
109
114
  fetchSiteConfig,