recipe-planner-ui 1.1.0 → 1.2.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.
Files changed (41) hide show
  1. package/dist/cjs/{index-B6uIqnrk.js → index-Cul1A0BY.js} +33 -1
  2. package/dist/cjs/loader.cjs.js +2 -2
  3. package/dist/cjs/recipe-planner-ui.cjs.js +2 -2
  4. package/dist/cjs/rp-day-slot.cjs.entry.js +77 -9
  5. package/dist/cjs/rp-filter-chips_5.cjs.entry.js +49 -12
  6. package/dist/collection/components/rp-day-slot/rp-day-slot.css +191 -23
  7. package/dist/collection/components/rp-day-slot/rp-day-slot.js +83 -11
  8. package/dist/collection/components/rp-filter-chips/rp-filter-chips.css +35 -3
  9. package/dist/collection/components/rp-filter-chips/rp-filter-chips.js +1 -1
  10. package/dist/collection/components/rp-ingredient-list/rp-ingredient-list.css +39 -9
  11. package/dist/collection/components/rp-ingredient-list/rp-ingredient-list.js +2 -2
  12. package/dist/collection/components/rp-modal/rp-modal.css +65 -12
  13. package/dist/collection/components/rp-modal/rp-modal.js +1 -1
  14. package/dist/collection/components/rp-recipe-card/rp-recipe-card.css +165 -16
  15. package/dist/collection/components/rp-recipe-card/rp-recipe-card.js +83 -1
  16. package/dist/collection/components/rp-search-bar/rp-search-bar.css +100 -18
  17. package/dist/collection/components/rp-search-bar/rp-search-bar.js +1 -1
  18. package/dist/components/index.js +1 -1
  19. package/dist/components/rp-day-slot.js +1 -1
  20. package/dist/components/rp-filter-chips.js +1 -1
  21. package/dist/components/rp-ingredient-list.js +1 -1
  22. package/dist/components/rp-modal.js +1 -1
  23. package/dist/components/rp-recipe-card.js +1 -1
  24. package/dist/components/rp-search-bar.js +1 -1
  25. package/dist/esm/{index-B4wAFfci.js → index-BxP2u8bq.js} +33 -1
  26. package/dist/esm/loader.js +3 -3
  27. package/dist/esm/recipe-planner-ui.js +3 -3
  28. package/dist/esm/rp-day-slot.entry.js +77 -9
  29. package/dist/esm/rp-filter-chips_5.entry.js +49 -12
  30. package/dist/recipe-planner-ui/p-1e3878fa.entry.js +1 -0
  31. package/dist/recipe-planner-ui/p-84819dee.entry.js +1 -0
  32. package/dist/recipe-planner-ui/p-BxP2u8bq.js +3 -0
  33. package/dist/recipe-planner-ui/recipe-planner-ui.css +6 -1
  34. package/dist/recipe-planner-ui/recipe-planner-ui.esm.js +1 -1
  35. package/dist/types/components/rp-day-slot/rp-day-slot.d.ts +22 -3
  36. package/dist/types/components/rp-recipe-card/rp-recipe-card.d.ts +20 -0
  37. package/dist/types/components.d.ts +35 -9
  38. package/package.json +1 -1
  39. package/dist/recipe-planner-ui/p-0c8f7438.entry.js +0 -1
  40. package/dist/recipe-planner-ui/p-B4wAFfci.js +0 -3
  41. package/dist/recipe-planner-ui/p-d54dbd37.entry.js +0 -1
@@ -591,6 +591,9 @@ var parsePropertyValue = (propValue, propType, isFormAssociated) => {
591
591
  return propValue === "false" ? false : propValue === "" || !!propValue;
592
592
  }
593
593
  }
594
+ if (propType & 2 /* Number */) {
595
+ return typeof propValue === "string" ? parseFloat(propValue) : typeof propValue === "number" ? propValue : NaN;
596
+ }
594
597
  if (propType & 1 /* String */) {
595
598
  return String(propValue);
596
599
  }
@@ -636,7 +639,11 @@ var setAccessor = (elm, memberName, oldValue, newValue, isSvg, flags, initialRen
636
639
  classList.remove(...oldClasses.filter((c) => c && !newClasses.includes(c)));
637
640
  classList.add(...newClasses.filter((c) => c && !oldClasses.includes(c)));
638
641
  }
639
- } else if (memberName === "key") ; else if ((!isProp ) && memberName[0] === "o" && memberName[1] === "n") {
642
+ } else if (memberName === "key") ; else if (memberName === "ref") {
643
+ if (newValue) {
644
+ queueRefAttachment(newValue, elm);
645
+ }
646
+ } else if ((!isProp ) && memberName[0] === "o" && memberName[1] === "n") {
640
647
  if (memberName[2] === "-") {
641
648
  memberName = memberName.slice(3);
642
649
  } else if (isMemberInElement(win, ln)) {
@@ -780,6 +787,8 @@ var useNativeShadowDom = false;
780
787
  var checkSlotFallbackVisibility = false;
781
788
  var checkSlotRelocate = false;
782
789
  var isSvgMode = false;
790
+ var refCallbacksToRemove = [];
791
+ var refCallbacksToAttach = [];
783
792
  var createElm = (oldParentVNode, newParentVNode, childIndex) => {
784
793
  var _a;
785
794
  const newVNode2 = newParentVNode.$children$[childIndex];
@@ -932,6 +941,7 @@ var removeVnodes = (vnodes, startIdx, endIdx) => {
932
941
  const vnode = vnodes[index];
933
942
  if (vnode) {
934
943
  const elm = vnode.$elm$;
944
+ nullifyVNodeRefs(vnode);
935
945
  if (elm) {
936
946
  {
937
947
  checkSlotFallbackVisibility = true;
@@ -1152,6 +1162,27 @@ var markSlotContentForRelocation = (elm) => {
1152
1162
  }
1153
1163
  }
1154
1164
  };
1165
+ var nullifyVNodeRefs = (vNode) => {
1166
+ {
1167
+ if (vNode.$attrs$ && vNode.$attrs$.ref) {
1168
+ refCallbacksToRemove.push(() => vNode.$attrs$.ref(null));
1169
+ }
1170
+ vNode.$children$ && vNode.$children$.map(nullifyVNodeRefs);
1171
+ }
1172
+ };
1173
+ var queueRefAttachment = (refCallback, elm) => {
1174
+ {
1175
+ refCallbacksToAttach.push(() => refCallback(elm));
1176
+ }
1177
+ };
1178
+ var flushQueuedRefCallbacks = () => {
1179
+ {
1180
+ refCallbacksToRemove.forEach((cb) => cb());
1181
+ refCallbacksToRemove.length = 0;
1182
+ refCallbacksToAttach.forEach((cb) => cb());
1183
+ refCallbacksToAttach.length = 0;
1184
+ }
1185
+ };
1155
1186
  var insertBefore = (parent, newNode, reference, isInitialLoad) => {
1156
1187
  {
1157
1188
  if (typeof newNode["s-sn"] === "string" && !!newNode["s-sr"] && !!newNode["s-cr"]) {
@@ -1321,6 +1352,7 @@ var renderVdom = (hostRef, renderFnResults, isInitialLoad = false) => {
1321
1352
  }
1322
1353
  }
1323
1354
  contentRef = void 0;
1355
+ flushQueuedRefCallbacks();
1324
1356
  };
1325
1357
 
1326
1358
  // src/runtime/update-component.ts
@@ -1,12 +1,12 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-B6uIqnrk.js');
3
+ var index = require('./index-Cul1A0BY.js');
4
4
  var appGlobals = require('./app-globals-V2Kpy_OQ.js');
5
5
 
6
6
  const defineCustomElements = async (win, options) => {
7
7
  if (typeof window === 'undefined') return undefined;
8
8
  await appGlobals.globalScripts();
9
- return index.bootstrapLazy([["rp-filter-chips_5.cjs",[[514,"rp-filter-chips",{"options":[16],"selected":[1],"label":[1]}],[774,"rp-ingredient-list",{"items":[16]}],[774,"rp-modal",{"open":[516],"heading":[1],"focusFirstField":[64]},null,{"open":[{"onOpenChange":0}]}],[774,"rp-recipe-card",{"recipeId":[1,"recipe-id"],"recipeTitle":[1,"recipe-title"],"image":[1],"category":[1],"favorite":[516]}],[514,"rp-search-bar",{"value":[1],"placeholder":[1],"label":[1],"draft":[32]},null,{"value":[{"onValueChange":0}]}]]],["rp-day-slot.cjs",[[774,"rp-day-slot",{"day":[1],"dayLabel":[1,"day-label"],"meals":[16],"days":[16],"dayLabels":[16],"normalized":[32]}]]]], options);
9
+ return index.bootstrapLazy([["rp-filter-chips_5.cjs",[[514,"rp-filter-chips",{"options":[16],"selected":[1],"label":[1]}],[774,"rp-ingredient-list",{"items":[16]}],[774,"rp-modal",{"open":[516],"heading":[1],"focusFirstField":[64]},null,{"open":[{"onOpenChange":0}]}],[774,"rp-recipe-card",{"recipeId":[1,"recipe-id"],"recipeTitle":[1,"recipe-title"],"image":[1],"category":[1],"area":[1],"minutes":[2],"favorite":[516],"imageLoading":[32],"imageFailed":[32],"pulsing":[32]}],[514,"rp-search-bar",{"value":[1],"placeholder":[1],"label":[1],"draft":[32]},null,{"value":[{"onValueChange":0}]}]]],["rp-day-slot.cjs",[[774,"rp-day-slot",{"day":[1],"dayLabel":[1,"day-label"],"meals":[16],"days":[16],"dayLabels":[16],"normalized":[32],"dropActive":[32],"draggingId":[32]}]]]], options);
10
10
  };
11
11
 
12
12
  exports.setNonce = index.setNonce;
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-B6uIqnrk.js');
3
+ var index = require('./index-Cul1A0BY.js');
4
4
  var appGlobals = require('./app-globals-V2Kpy_OQ.js');
5
5
 
6
6
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
@@ -19,7 +19,7 @@ var patchBrowser = () => {
19
19
 
20
20
  patchBrowser().then(async (options) => {
21
21
  await appGlobals.globalScripts();
22
- return index.bootstrapLazy([["rp-filter-chips_5.cjs",[[514,"rp-filter-chips",{"options":[16],"selected":[1],"label":[1]}],[774,"rp-ingredient-list",{"items":[16]}],[774,"rp-modal",{"open":[516],"heading":[1],"focusFirstField":[64]},null,{"open":[{"onOpenChange":0}]}],[774,"rp-recipe-card",{"recipeId":[1,"recipe-id"],"recipeTitle":[1,"recipe-title"],"image":[1],"category":[1],"favorite":[516]}],[514,"rp-search-bar",{"value":[1],"placeholder":[1],"label":[1],"draft":[32]},null,{"value":[{"onValueChange":0}]}]]],["rp-day-slot.cjs",[[774,"rp-day-slot",{"day":[1],"dayLabel":[1,"day-label"],"meals":[16],"days":[16],"dayLabels":[16],"normalized":[32]}]]]], options);
22
+ return index.bootstrapLazy([["rp-filter-chips_5.cjs",[[514,"rp-filter-chips",{"options":[16],"selected":[1],"label":[1]}],[774,"rp-ingredient-list",{"items":[16]}],[774,"rp-modal",{"open":[516],"heading":[1],"focusFirstField":[64]},null,{"open":[{"onOpenChange":0}]}],[774,"rp-recipe-card",{"recipeId":[1,"recipe-id"],"recipeTitle":[1,"recipe-title"],"image":[1],"category":[1],"area":[1],"minutes":[2],"favorite":[516],"imageLoading":[32],"imageFailed":[32],"pulsing":[32]}],[514,"rp-search-bar",{"value":[1],"placeholder":[1],"label":[1],"draft":[32]},null,{"value":[{"onValueChange":0}]}]]],["rp-day-slot.cjs",[[774,"rp-day-slot",{"day":[1],"dayLabel":[1,"day-label"],"meals":[16],"days":[16],"dayLabels":[16],"normalized":[32],"dropActive":[32],"draggingId":[32]}]]]], options);
23
23
  });
24
24
 
25
25
  exports.setNonce = index.setNonce;
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-B6uIqnrk.js');
3
+ var index = require('./index-Cul1A0BY.js');
4
4
 
5
- const rpDaySlotCss = () => `.sc-rp-day-slot-h{display:block;font-family:var(--rp-font-sans);color:var(--rp-color-text)}.slot.sc-rp-day-slot{display:flex;flex-direction:column;height:100%;min-height:160px;background:var(--rp-color-surface);border:1px solid var(--rp-color-border);border-radius:var(--rp-radius-md)}.head.sc-rp-day-slot{display:flex;gap:var(--rp-space-2);align-items:center;justify-content:space-between;padding:var(--rp-space-2) var(--rp-space-3);border-bottom:1px solid var(--rp-color-border)}.day.sc-rp-day-slot{margin:0;font-size:var(--rp-font-size-md);text-transform:capitalize}.add.sc-rp-day-slot{display:grid;place-items:center;width:28px;height:28px;padding:0;font-size:1.125rem;line-height:1;color:var(--rp-color-accent-contrast);cursor:pointer;background:var(--rp-color-accent);border:none;border-radius:50%}.meals.sc-rp-day-slot{flex:1;margin:0;padding:var(--rp-space-2);list-style:none;display:flex;flex-direction:column;gap:var(--rp-space-2)}.meal.sc-rp-day-slot{padding:var(--rp-space-2);background:var(--rp-color-surface-sunken);border-radius:var(--rp-radius-sm)}.meal-title.sc-rp-day-slot{display:block;font-size:var(--rp-font-size-sm);line-height:1.35}.meal-actions.sc-rp-day-slot{display:flex;gap:var(--rp-space-1);align-items:center;justify-content:space-between;margin-top:var(--rp-space-2)}.move-select.sc-rp-day-slot{max-width:100%;padding:2px var(--rp-space-1);font:inherit;font-size:var(--rp-font-size-sm);color:var(--rp-color-text-muted);background:var(--rp-color-surface);border:1px solid var(--rp-color-border);border-radius:var(--rp-radius-sm);text-transform:capitalize}.remove.sc-rp-day-slot{display:grid;place-items:center;width:24px;height:24px;padding:0;font-size:1.125rem;line-height:1;color:var(--rp-color-danger);cursor:pointer;background:none;border:none;border-radius:var(--rp-radius-sm)}.empty.sc-rp-day-slot{display:grid;flex:1;place-items:center;padding:var(--rp-space-3)}.empty-text.sc-rp-day-slot{margin:0;font-size:var(--rp-font-size-sm);color:var(--rp-color-text-muted)}.add.sc-rp-day-slot:focus-visible,.remove.sc-rp-day-slot:focus-visible,.move-select.sc-rp-day-slot:focus-visible{outline:var(--rp-focus-ring);outline-offset:var(--rp-focus-offset)}.sr-only.sc-rp-day-slot{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}`;
5
+ const rpDaySlotCss = () => `.sc-rp-day-slot-h{display:block;font-family:var(--rp-font-sans);color:var(--rp-color-text)}.slot.sc-rp-day-slot{display:flex;flex-direction:column;height:100%;min-height:200px;overflow:hidden;background:var(--rp-color-surface);border:1px solid var(--rp-color-border);border-radius:var(--rp-radius-lg);box-shadow:var(--rp-shadow-xs);transition:border-color var(--rp-duration) var(--rp-ease), box-shadow var(--rp-duration) var(--rp-ease), background-color var(--rp-duration) var(--rp-ease)}.slot.is-empty.sc-rp-day-slot{background:var(--rp-color-surface-sunken);border-style:dashed}.slot.is-drop-active.sc-rp-day-slot{background:var(--rp-color-highlight-soft);border-style:solid;border-color:var(--rp-color-highlight);box-shadow:var(--rp-shadow-md), inset 0 0 0 1px var(--rp-color-highlight)}.head.sc-rp-day-slot{display:flex;gap:var(--rp-space-2);align-items:center;justify-content:space-between;padding:var(--rp-space-3);border-bottom:1px solid var(--rp-color-border)}.day.sc-rp-day-slot{min-width:0;margin:0;overflow:hidden;font-size:var(--rp-font-size-xs);font-weight:700;letter-spacing:0.08em;text-transform:uppercase;text-overflow:ellipsis;white-space:nowrap;color:var(--rp-color-text-muted)}.head-right.sc-rp-day-slot{display:flex;flex-shrink:0;gap:var(--rp-space-2);align-items:center}.count.sc-rp-day-slot{display:grid;place-items:center;min-width:20px;height:20px;padding:0 6px;font-size:0.6875rem;font-weight:700;font-variant-numeric:tabular-nums;color:var(--rp-caramel-600);background:var(--rp-color-highlight-soft);border-radius:var(--rp-radius-pill)}.add.sc-rp-day-slot{display:grid;place-items:center;width:26px;height:26px;padding:0;cursor:pointer;color:var(--rp-color-accent-contrast);background:var(--rp-color-accent);border:none;border-radius:50%;transition:transform var(--rp-duration-fast) var(--rp-ease), background-color var(--rp-duration-fast) var(--rp-ease)}.add.sc-rp-day-slot svg.sc-rp-day-slot{fill:none;stroke:currentColor;stroke-width:2.2;stroke-linecap:round}.add.sc-rp-day-slot:hover{background:var(--rp-color-highlight);transform:rotate(90deg) scale(1.1)}.add.sc-rp-day-slot:active{transform:rotate(90deg) scale(0.92)}.meals.sc-rp-day-slot{flex:1;margin:0;padding:var(--rp-space-2);list-style:none;display:flex;flex-direction:column;gap:var(--rp-space-2)}.meal.sc-rp-day-slot{position:relative;padding:var(--rp-space-3) var(--rp-space-3) var(--rp-space-2);cursor:grab;background:var(--rp-color-surface-sunken);border:1px solid var(--rp-color-border);border-radius:var(--rp-radius-md);transition:transform var(--rp-duration-fast) var(--rp-ease), box-shadow var(--rp-duration-fast) var(--rp-ease), opacity var(--rp-duration-fast) var(--rp-ease)}.meal.sc-rp-day-slot:hover{background:var(--rp-color-surface);box-shadow:var(--rp-shadow-sm);transform:translateY(-1px)}.meal.sc-rp-day-slot:active{cursor:grabbing}.meal.is-dragging.sc-rp-day-slot{opacity:0.4;box-shadow:none;transform:none}.grip.sc-rp-day-slot{position:absolute;inset-block-start:var(--rp-space-2);inset-inline-end:var(--rp-space-2);display:block;opacity:0;transition:opacity var(--rp-duration-fast) var(--rp-ease)}.grip.sc-rp-day-slot svg.sc-rp-day-slot{display:block;fill:var(--rp-color-text-subtle)}.meal.sc-rp-day-slot:hover .grip.sc-rp-day-slot{opacity:1}.meal-title.sc-rp-day-slot{display:block;padding-inline-end:var(--rp-space-4);font-size:var(--rp-font-size-sm);font-weight:500;line-height:1.35;color:var(--rp-color-text)}.meal-actions.sc-rp-day-slot{display:flex;gap:var(--rp-space-1);align-items:center;justify-content:space-between;margin-top:var(--rp-space-2)}.move-select.sc-rp-day-slot{max-width:100%;padding:3px var(--rp-space-2);font:inherit;font-size:var(--rp-font-size-xs);color:var(--rp-color-text-muted);cursor:pointer;background:var(--rp-color-surface);border:1px solid var(--rp-color-border);border-radius:var(--rp-radius-sm);transition:border-color var(--rp-duration-fast) var(--rp-ease)}.move-select.sc-rp-day-slot:hover{border-color:var(--rp-color-border-strong)}.remove.sc-rp-day-slot{display:grid;place-items:center;width:24px;height:24px;padding:0;color:var(--rp-color-text-subtle);cursor:pointer;background:none;border:none;border-radius:var(--rp-radius-sm);transition:color var(--rp-duration-fast) var(--rp-ease), background-color var(--rp-duration-fast) var(--rp-ease)}.remove.sc-rp-day-slot svg.sc-rp-day-slot{fill:none;stroke:currentColor;stroke-width:2.2;stroke-linecap:round}.remove.sc-rp-day-slot:hover{color:var(--rp-color-danger);background:var(--rp-color-danger-soft)}.empty.sc-rp-day-slot{display:flex;flex:1;flex-direction:column;gap:var(--rp-space-2);align-items:center;justify-content:center;padding:var(--rp-space-4) var(--rp-space-3);text-align:center}.empty-icon.sc-rp-day-slot{fill:none;stroke:var(--rp-color-text-subtle);stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}.empty-text.sc-rp-day-slot{margin:0;font-size:var(--rp-font-size-xs);color:var(--rp-color-text-subtle)}.add.sc-rp-day-slot:focus-visible,.remove.sc-rp-day-slot:focus-visible,.move-select.sc-rp-day-slot:focus-visible{outline:var(--rp-focus-ring);outline-offset:var(--rp-focus-offset)}.sr-only.sc-rp-day-slot{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}@media (prefers-reduced-motion: reduce){.slot.sc-rp-day-slot,.meal.sc-rp-day-slot,.add.sc-rp-day-slot,.grip.sc-rp-day-slot,.remove.sc-rp-day-slot,.move-select.sc-rp-day-slot{transition:none}.meal.sc-rp-day-slot:hover,.add.sc-rp-day-slot:hover,.add.sc-rp-day-slot:active{transform:none}.grip.sc-rp-day-slot{opacity:1}}`;
6
6
 
7
7
  const DaySlot = class {
8
8
  constructor(hostRef) {
@@ -27,11 +27,20 @@ const DaySlot = class {
27
27
  */
28
28
  dayLabels = {};
29
29
  normalized = [];
30
+ /** True while a compatible meal is being dragged over this column. */
31
+ dropActive = false;
32
+ /** Id of the meal being dragged out of this column, dimmed at its origin. */
33
+ draggingId = null;
34
+ /**
35
+ * Nested children fire dragleave as the pointer crosses them, so a boolean flag would
36
+ * flicker off mid-column. Counting enter and leave pairs is what makes the state stable.
37
+ */
38
+ dragDepth = 0;
30
39
  /** Fired when the user asks to add a meal to this day. */
31
40
  rpAddMeal;
32
41
  /** Fired when a meal is removed from this day. */
33
42
  rpRemoveMeal;
34
- /** Fired when a meal is reassigned to a different day. */
43
+ /** Fired when a meal is reassigned to a different day, by drag or by select. */
35
44
  rpMoveMeal;
36
45
  componentWillLoad() {
37
46
  this.normalized = Array.isArray(this.meals) ? this.meals : [];
@@ -41,23 +50,82 @@ const DaySlot = class {
41
50
  // replaced before the element upgrades, which @Watch would miss.
42
51
  this.normalized = Array.isArray(this.meals) ? this.meals : [];
43
52
  }
53
+ emitMove(id, from, to) {
54
+ if (!to || to === from)
55
+ return;
56
+ this.rpMoveMeal.emit({ id, from, to });
57
+ }
44
58
  onMove = (event, meal) => {
45
59
  const select = event.target;
46
- const to = select.value;
47
- if (!to || to === this.day)
48
- return;
49
- this.rpMoveMeal.emit({ id: meal.id, from: this.day, to });
60
+ this.emitMove(meal.id, this.day, select.value);
50
61
  // The consumer re-renders from its own data; resetting avoids a stale selection
51
62
  // if that render is delayed.
52
63
  select.value = '';
53
64
  };
65
+ onDragStart = (event, meal) => {
66
+ this.draggingId = meal.id;
67
+ // A custom MIME type is what lets dragover distinguish a meal from a dragged file or
68
+ // a text selection, so unrelated drags never light up a column as a drop target.
69
+ event.dataTransfer?.setData('application/x-rp-meal', JSON.stringify({ id: meal.id, from: this.day }));
70
+ // text/plain is set alongside it because some browsers refuse a drag with no
71
+ // conventional format attached.
72
+ event.dataTransfer?.setData('text/plain', meal.title);
73
+ if (event.dataTransfer)
74
+ event.dataTransfer.effectAllowed = 'move';
75
+ };
76
+ onDragEnd = () => {
77
+ this.draggingId = null;
78
+ this.dragDepth = 0;
79
+ this.dropActive = false;
80
+ };
81
+ hasMealPayload(event) {
82
+ return event.dataTransfer?.types.includes('application/x-rp-meal') ?? false;
83
+ }
84
+ onDragEnter = (event) => {
85
+ if (!this.hasMealPayload(event))
86
+ return;
87
+ this.dragDepth += 1;
88
+ this.dropActive = true;
89
+ };
90
+ onDragOver = (event) => {
91
+ if (!this.hasMealPayload(event))
92
+ return;
93
+ // Without preventDefault the browser treats the element as a non-drop zone and the
94
+ // drop event never fires.
95
+ event.preventDefault();
96
+ if (event.dataTransfer)
97
+ event.dataTransfer.dropEffect = 'move';
98
+ };
99
+ onDragLeave = (event) => {
100
+ if (!this.hasMealPayload(event))
101
+ return;
102
+ this.dragDepth = Math.max(0, this.dragDepth - 1);
103
+ if (this.dragDepth === 0)
104
+ this.dropActive = false;
105
+ };
106
+ onDrop = (event) => {
107
+ const payload = event.dataTransfer?.getData('application/x-rp-meal');
108
+ if (!payload)
109
+ return;
110
+ event.preventDefault();
111
+ this.dragDepth = 0;
112
+ this.dropActive = false;
113
+ try {
114
+ const { id, from } = JSON.parse(payload);
115
+ this.emitMove(id, from, this.day);
116
+ }
117
+ catch {
118
+ // A malformed payload means the drag did not originate here; ignoring it is
119
+ // preferable to throwing inside a drop handler, where nothing would catch it.
120
+ }
121
+ };
54
122
  render() {
55
123
  const days = Array.isArray(this.days) ? this.days : [];
56
124
  const labels = this.dayLabels ?? {};
57
125
  const meals = this.normalized;
58
- return (index.h(index.Host, { key: '978933e713e714278d4913516ec1c1aca1593513' }, index.h("section", { key: '7f035218a49d75e8d96f675259d33de09a504334', class: "slot" }, index.h("header", { key: 'c7d620e25558ca4758f6d67ac3eaf74f1f1f756c', class: "head" }, index.h("h3", { key: 'b17841e7792ffc8c7179a395d7dc2219781179b5', class: "day" }, this.dayLabel || this.day), index.h("button", { key: 'f336aac8ff55e5d596c4d99a954218613b6686a2', type: "button", class: "add", onClick: () => this.rpAddMeal.emit(this.day), "aria-label": `Add a meal to ${this.dayLabel || this.day}` }, "+")), meals.length === 0 ? (index.h("div", { class: "empty" }, index.h("slot", null, index.h("p", { class: "empty-text" }, "No meals planned")))) : (index.h("ul", { class: "meals" }, meals.map((meal) => (index.h("li", { class: "meal", key: meal.id }, index.h("span", { class: "meal-title" }, meal.title), index.h("div", { class: "meal-actions" }, index.h("label", { class: "move" }, index.h("span", { class: "sr-only" }, `Move ${meal.title} to another day`), index.h("select", { class: "move-select", onChange: (event) => this.onMove(event, meal) }, index.h("option", { value: "" }, "Move\u2026"), days
126
+ return (index.h(index.Host, { key: '755cdc190abccb9b0350caf7e4fad05675dd6e0e' }, index.h("section", { key: '927c1e962ce3f918e001965da10bb39b82cab5bc', class: { slot: true, 'is-drop-active': this.dropActive, 'is-empty': meals.length === 0 }, onDragEnter: this.onDragEnter, onDragOver: this.onDragOver, onDragLeave: this.onDragLeave, onDrop: this.onDrop }, index.h("header", { key: '415e9d56f9b190fd291af942c01427cc4b9b66ea', class: "head" }, index.h("h3", { key: 'd0f20de86c70d85da545ec9ec9c57790961b78f0', class: "day" }, this.dayLabel || this.day), index.h("div", { key: '484a1e743a3f251865288f345ead14d87741ff8e', class: "head-right" }, meals.length > 0 && index.h("span", { key: 'ac3324168f0061f4bb173090e21c4b6f2334a168', class: "count" }, meals.length), index.h("button", { key: '5ca123821d1b90cc822bb3036884ce93f8cae19c', type: "button", class: "add", onClick: () => this.rpAddMeal.emit(this.day), "aria-label": `Add a meal to ${this.dayLabel || this.day}` }, index.h("svg", { key: 'e7b09c139cfd3106a83481a1bbb48bf99e82d47c', viewBox: "0 0 24 24", width: "15", height: "15", "aria-hidden": "true" }, index.h("path", { key: '06c62bb2b09a4c0676977411644d15d4217361a8', d: "M12 5v14M5 12h14" }))))), meals.length === 0 ? (index.h("div", { class: "empty" }, index.h("slot", null, index.h("svg", { class: "empty-icon", viewBox: "0 0 24 24", width: "26", height: "26", "aria-hidden": "true" }, index.h("path", { d: "M4 4h16v16H4z", opacity: "0.35" }), index.h("path", { d: "M8 2v4M16 2v4M4 10h16" })), index.h("p", { class: "empty-text" }, "No meals planned")))) : (index.h("ul", { class: "meals" }, meals.map((meal) => (index.h("li", { class: { meal: true, 'is-dragging': this.draggingId === meal.id }, key: meal.id, draggable: true, onDragStart: (event) => this.onDragStart(event, meal), onDragEnd: this.onDragEnd }, index.h("span", { class: "grip", "aria-hidden": "true" }, index.h("svg", { viewBox: "0 0 24 24", width: "12", height: "12" }, index.h("circle", { cx: "9", cy: "6", r: "1.4" }), index.h("circle", { cx: "15", cy: "6", r: "1.4" }), index.h("circle", { cx: "9", cy: "12", r: "1.4" }), index.h("circle", { cx: "15", cy: "12", r: "1.4" }), index.h("circle", { cx: "9", cy: "18", r: "1.4" }), index.h("circle", { cx: "15", cy: "18", r: "1.4" }))), index.h("span", { class: "meal-title" }, meal.title), index.h("div", { class: "meal-actions" }, index.h("label", { class: "move" }, index.h("span", { class: "sr-only" }, `Move ${meal.title} to another day`), index.h("select", { class: "move-select", onChange: (event) => this.onMove(event, meal) }, index.h("option", { value: "" }, "Move\u2026"), days
59
127
  .filter((day) => day !== this.day)
60
- .map((day) => (index.h("option", { key: day, value: day }, labels[day] ?? day))))), index.h("button", { type: "button", class: "remove", onClick: () => this.rpRemoveMeal.emit({ id: meal.id, day: this.day }), "aria-label": `Remove ${meal.title}` }, "\u00D7"))))))))));
128
+ .map((day) => (index.h("option", { key: day, value: day }, labels[day] ?? day))))), index.h("button", { type: "button", class: "remove", onClick: () => this.rpRemoveMeal.emit({ id: meal.id, day: this.day }), "aria-label": `Remove ${meal.title}` }, index.h("svg", { viewBox: "0 0 24 24", width: "13", height: "13", "aria-hidden": "true" }, index.h("path", { d: "M6 6l12 12M18 6L6 18" }))))))))))));
61
129
  }
62
130
  };
63
131
  DaySlot.style = rpDaySlotCss();
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-B6uIqnrk.js');
3
+ var index = require('./index-Cul1A0BY.js');
4
4
 
5
- const rpFilterChipsCss = () => `.sc-rp-filter-chips-h{display:block;font-family:var(--rp-font-sans)}.chips.sc-rp-filter-chips{display:flex;flex-wrap:wrap;gap:var(--rp-space-2)}.chip.sc-rp-filter-chips{padding:var(--rp-space-1) var(--rp-space-3);font:inherit;font-size:var(--rp-font-size-sm);color:var(--rp-color-text);cursor:pointer;background:var(--rp-color-surface);border:1px solid var(--rp-color-border);border-radius:999px}.chip.sc-rp-filter-chips:hover{background:var(--rp-color-surface-sunken)}.chip.is-selected.sc-rp-filter-chips{color:var(--rp-color-accent-contrast);background:var(--rp-color-accent);border-color:var(--rp-color-accent)}.chip.sc-rp-filter-chips:focus-visible{outline:var(--rp-focus-ring);outline-offset:var(--rp-focus-offset)}`;
5
+ const rpFilterChipsCss = () => `.sc-rp-filter-chips-h{display:block;font-family:var(--rp-font-sans)}.chips.sc-rp-filter-chips{display:flex;flex-wrap:wrap;gap:var(--rp-space-2)}.chip.sc-rp-filter-chips{padding:6px var(--rp-space-4);font:inherit;font-size:var(--rp-font-size-sm);font-weight:500;color:var(--rp-color-text-body);cursor:pointer;background:var(--rp-color-surface);border:1px solid var(--rp-color-border);border-radius:var(--rp-radius-pill);transition:color var(--rp-duration-fast) var(--rp-ease), background-color var(--rp-duration-fast) var(--rp-ease), border-color var(--rp-duration-fast) var(--rp-ease), transform var(--rp-duration-fast) var(--rp-ease), box-shadow var(--rp-duration-fast) var(--rp-ease)}.chip.sc-rp-filter-chips:hover{color:var(--rp-color-text);background:var(--rp-color-surface-sunken);border-color:var(--rp-color-border-strong);transform:translateY(-1px)}.chip.sc-rp-filter-chips:active{transform:translateY(0) scale(0.97)}.chip.is-selected.sc-rp-filter-chips{color:var(--rp-color-accent-contrast);background:var(--rp-color-accent);border-color:var(--rp-color-accent);box-shadow:var(--rp-shadow-sm)}.chip.is-selected.sc-rp-filter-chips:hover{color:var(--rp-color-accent-contrast);background:var(--rp-color-accent-hover);border-color:var(--rp-color-accent-hover)}.chip.sc-rp-filter-chips:focus-visible{outline:var(--rp-focus-ring);outline-offset:var(--rp-focus-offset)}@media (prefers-reduced-motion: reduce){.chip.sc-rp-filter-chips{transition:none}.chip.sc-rp-filter-chips:hover,.chip.sc-rp-filter-chips:active{transform:none}}`;
6
6
 
7
7
  const FilterChips = class {
8
8
  constructor(hostRef) {
@@ -29,12 +29,12 @@ const FilterChips = class {
29
29
  // A string prop arriving from an HTML attribute would be unusable; tolerate it so
30
30
  // the component behaves predictably in a plain .html page.
31
31
  const options = Array.isArray(this.options) ? this.options : [];
32
- return (index.h(index.Host, { key: '656b969af2e55a7c4313bc0c2c422e000811af53' }, index.h("div", { key: '1da1173fc1d6d10a312a43466a0ea799b625caef', class: "chips", role: "group", "aria-label": this.label }, options.map((option) => (index.h("button", { key: option, type: "button", class: { chip: true, 'is-selected': this.selected === option }, "aria-pressed": String(this.selected === option), onClick: () => this.select(option) }, option))))));
32
+ return (index.h(index.Host, { key: 'f695ec8ae5603061f1fcf027257563c416f8fde8' }, index.h("div", { key: '80b59e2aaedd649ec387534da5dd3dc5d9955be5', class: "chips", role: "group", "aria-label": this.label }, options.map((option) => (index.h("button", { key: option, type: "button", class: { chip: true, 'is-selected': this.selected === option }, "aria-pressed": String(this.selected === option), onClick: () => this.select(option) }, option))))));
33
33
  }
34
34
  };
35
35
  FilterChips.style = rpFilterChipsCss();
36
36
 
37
- const rpIngredientListCss = () => `.sc-rp-ingredient-list-h{display:block;font-family:var(--rp-font-sans);color:var(--rp-color-text)}.heading.sc-rp-ingredient-list{margin:0 0 var(--rp-space-3);font-size:var(--rp-font-size-lg)}.list.sc-rp-ingredient-list{margin:0;padding:0;list-style:none;border:1px solid var(--rp-color-border);border-radius:var(--rp-radius-md);overflow:hidden}.row.sc-rp-ingredient-list{display:flex;justify-content:space-between;gap:var(--rp-space-4);padding:var(--rp-space-2) var(--rp-space-3);font-size:var(--rp-font-size-md)}.row.sc-rp-ingredient-list:nth-child(odd){background:var(--rp-color-surface-sunken)}.measure.sc-rp-ingredient-list{flex-shrink:0;color:var(--rp-color-text-muted);font-variant-numeric:tabular-nums}.empty.sc-rp-ingredient-list{margin:0;font-size:var(--rp-font-size-md);color:var(--rp-color-text-muted)}.note.sc-rp-ingredient-list{margin-top:var(--rp-space-2);font-size:var(--rp-font-size-sm);color:var(--rp-color-text-muted)}.note.sc-rp-ingredient-list:not(:has(*)){display:none}`;
37
+ const rpIngredientListCss = () => `.sc-rp-ingredient-list-h{display:block;font-family:var(--rp-font-sans);color:var(--rp-color-text)}.heading.sc-rp-ingredient-list{margin:0 0 var(--rp-space-4);font-family:var(--rp-font-serif);font-size:var(--rp-font-size-xl);font-weight:600;letter-spacing:-0.015em}.list.sc-rp-ingredient-list{margin:0;padding:var(--rp-space-2);list-style:none;background:var(--rp-color-surface);border:1px solid var(--rp-color-border);border-radius:var(--rp-radius-lg);box-shadow:var(--rp-shadow-xs);overflow:hidden}.row.sc-rp-ingredient-list{display:flex;justify-content:space-between;gap:var(--rp-space-4);padding:var(--rp-space-3);font-size:var(--rp-font-size-md);border-radius:var(--rp-radius-sm);transition:background-color var(--rp-duration-fast) var(--rp-ease)}.row.sc-rp-ingredient-list:hover{background:var(--rp-color-highlight-soft)}.name.sc-rp-ingredient-list{color:var(--rp-color-text-body)}.measure.sc-rp-ingredient-list{flex-shrink:0;font-size:var(--rp-font-size-sm);font-weight:600;color:var(--rp-caramel-600);font-variant-numeric:tabular-nums}.empty.sc-rp-ingredient-list{margin:0;padding:var(--rp-space-5);font-size:var(--rp-font-size-md);color:var(--rp-color-text-muted);text-align:center;background:var(--rp-color-surface-sunken);border:1px dashed var(--rp-color-border-strong);border-radius:var(--rp-radius-lg)}.note.sc-rp-ingredient-list{margin-top:var(--rp-space-3);font-size:var(--rp-font-size-sm);color:var(--rp-color-text-muted);text-align:center}.note.sc-rp-ingredient-list:not(:has(*)){display:none}@media (prefers-reduced-motion: reduce){.row.sc-rp-ingredient-list{transition:none}}`;
38
38
 
39
39
  const IngredientList = class {
40
40
  constructor(hostRef) {
@@ -44,15 +44,15 @@ const IngredientList = class {
44
44
  items = [];
45
45
  render() {
46
46
  const items = Array.isArray(this.items) ? this.items : [];
47
- return (index.h(index.Host, { key: '22708ae6e60de472b2f8e40fb54b33a2ec691ad1' }, index.h("section", { key: '6f922fc37e3c230511bbdf72e55826dfa73e167e', class: "wrap" }, index.h("slot", { key: 'e9f0b16183f5ded825a5c1663caa54dcb6c2d961', name: "heading" }, index.h("h2", { key: 'c68ebf97056bc6309d3c4e79d1796d89eede1325', class: "heading" }, "Ingredients")), items.length === 0 ? (index.h("p", { class: "empty" }, "No ingredients listed for this recipe.")) : (index.h("ul", { class: "list" }, items.map((item, index$1) => (
47
+ return (index.h(index.Host, { key: '908db0a9d14068fff426be9e86908c67295ef413' }, index.h("section", { key: '28f9022e75e767ba85a2f9fcfaa23f6a14d83576', class: "wrap" }, index.h("slot", { key: '0909fca0976d34c2e5ccbc7c935bc7b1148480ec', name: "heading" }, index.h("h2", { key: '61922c8539936be7bfcbbd27d658ef0085b19c80', class: "heading" }, "Ingredients")), items.length === 0 ? (index.h("p", { class: "empty" }, "No ingredients listed for this recipe.")) : (index.h("ul", { class: "list" }, items.map((item, index$1) => (
48
48
  // Ingredient names repeat within a recipe (for example, oil listed twice
49
49
  // with different measures), so the index is part of the identity.
50
- index.h("li", { class: "row", key: `${item.name}-${index$1}` }, index.h("span", { class: "name" }, item.name), index.h("span", { class: "measure" }, item.measure)))))), index.h("div", { key: '28d26e1bbb9f14e5dedfb153ff87e44add5cfbfb', class: "note" }, index.h("slot", { key: '20410e710e3c7dbc2ee9f6ad9493984382693687' })))));
50
+ index.h("li", { class: "row", key: `${item.name}-${index$1}` }, index.h("span", { class: "name" }, item.name), index.h("span", { class: "measure" }, item.measure)))))), index.h("div", { key: 'e394fb29b9484d890dba642c6079e10621550843', class: "note" }, index.h("slot", { key: '92269d3f3158ae3a1f1ea8b86a37d2b1a66da069' })))));
51
51
  }
52
52
  };
53
53
  IngredientList.style = rpIngredientListCss();
54
54
 
55
- const rpModalCss = () => `.sc-rp-modal-h{display:contents;font-family:var(--rp-font-sans)}.backdrop.sc-rp-modal{position:fixed;inset:0;z-index:100;display:grid;place-items:center;padding:var(--rp-space-4);background:rgb(0 0 0 / 0.5)}.dialog.sc-rp-modal{display:flex;flex-direction:column;width:min(560px, 100%);max-height:min(80vh, 640px);color:var(--rp-color-text);background:var(--rp-color-surface);border-radius:var(--rp-radius-lg);box-shadow:var(--rp-shadow-lg)}.head.sc-rp-modal{display:flex;gap:var(--rp-space-3);align-items:center;justify-content:space-between;padding:var(--rp-space-4);border-bottom:1px solid var(--rp-color-border)}.heading.sc-rp-modal{margin:0;font-size:var(--rp-font-size-lg)}.close.sc-rp-modal{display:grid;place-items:center;width:32px;height:32px;padding:0;font-size:1.5rem;line-height:1;color:var(--rp-color-text-muted);cursor:pointer;background:none;border:none;border-radius:var(--rp-radius-sm)}.close.sc-rp-modal:hover{background:var(--rp-color-surface-sunken)}.close.sc-rp-modal:focus-visible{outline:var(--rp-focus-ring);outline-offset:var(--rp-focus-offset)}.body.sc-rp-modal{flex:1;padding:var(--rp-space-4);overflow-y:auto}.foot.sc-rp-modal{display:flex;gap:var(--rp-space-2);justify-content:flex-end;padding:var(--rp-space-4);border-top:1px solid var(--rp-color-border)}.foot.sc-rp-modal:not(:has(*)){display:none}@media (prefers-reduced-motion: no-preference){.dialog.sc-rp-modal{animation:rp-modal-in 120ms ease-out}@keyframes rp-modal-in{from{opacity:0;transform:translateY(8px)}}}`;
55
+ const rpModalCss = () => `.sc-rp-modal-h{display:contents;font-family:var(--rp-font-sans)}.sc-rp-modal-h:not([open]){display:none}.backdrop.sc-rp-modal{position:fixed;inset:0;z-index:100;display:grid;place-items:center;padding:var(--rp-space-4);background:rgb(44 24 16 / 0.42);backdrop-filter:blur(6px)}.dialog.sc-rp-modal{display:flex;flex-direction:column;width:min(560px, 100%);max-height:min(80vh, 640px);overflow:hidden;color:var(--rp-color-text);background:var(--rp-color-surface);border:1px solid var(--rp-color-border);border-radius:var(--rp-radius-xl);box-shadow:var(--rp-shadow-xl)}.head.sc-rp-modal{display:flex;gap:var(--rp-space-3);align-items:center;justify-content:space-between;padding:var(--rp-space-5) var(--rp-space-5) var(--rp-space-4);border-bottom:1px solid var(--rp-color-border)}.heading.sc-rp-modal{margin:0;font-family:var(--rp-font-serif);font-size:var(--rp-font-size-xl);font-weight:600;letter-spacing:-0.015em}.close.sc-rp-modal{display:grid;place-items:center;flex-shrink:0;width:32px;height:32px;padding:0;color:var(--rp-color-text-muted);cursor:pointer;background:none;border:none;border-radius:50%;transition:color var(--rp-duration-fast) var(--rp-ease), background-color var(--rp-duration-fast) var(--rp-ease), transform var(--rp-duration-fast) var(--rp-ease)}.close.sc-rp-modal svg.sc-rp-modal{fill:none;stroke:currentColor;stroke-width:2.2;stroke-linecap:round}.close.sc-rp-modal:hover{color:var(--rp-color-text);background:var(--rp-color-surface-sunken);transform:rotate(90deg)}.close.sc-rp-modal:focus-visible{outline:var(--rp-focus-ring);outline-offset:var(--rp-focus-offset)}.body.sc-rp-modal{flex:1;padding:var(--rp-space-5);font-size:var(--rp-font-size-md);color:var(--rp-color-text-body);overflow-y:auto}.foot.sc-rp-modal{display:flex;gap:var(--rp-space-2);justify-content:flex-end;padding:var(--rp-space-4) var(--rp-space-5) var(--rp-space-5);background:var(--rp-color-surface-sunken);border-top:1px solid var(--rp-color-border)}.foot.sc-rp-modal:not(:has(*)){display:none}@media (prefers-reduced-motion: no-preference){.backdrop.sc-rp-modal{animation:rp-backdrop-in var(--rp-duration) var(--rp-ease)}.dialog.sc-rp-modal{animation:rp-modal-in var(--rp-duration) var(--rp-ease)}@keyframes rp-backdrop-in{from{opacity:0}}@keyframes rp-modal-in{from{opacity:0;transform:translateY(12px) scale(0.97)}}}@media (prefers-reduced-motion: reduce){.close.sc-rp-modal{transition:none}.close.sc-rp-modal:hover{transform:none}}`;
56
56
 
57
57
  const FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
58
58
  const Modal = class {
@@ -135,7 +135,7 @@ const Modal = class {
135
135
  render() {
136
136
  if (!this.open)
137
137
  return null;
138
- return (index.h(index.Host, null, index.h("div", { class: "backdrop", onClick: this.onBackdropClick }, index.h("div", { class: "dialog", role: "dialog", "aria-modal": "true", "aria-label": this.heading }, index.h("header", { class: "head" }, index.h("h2", { class: "heading" }, this.heading), index.h("button", { type: "button", class: "close", onClick: () => this.rpClose.emit(), "aria-label": "Close dialog" }, "\u00D7")), index.h("div", { class: "body" }, index.h("slot", null)), index.h("footer", { class: "foot" }, index.h("slot", { name: "footer" }))))));
138
+ return (index.h(index.Host, null, index.h("div", { class: "backdrop", onClick: this.onBackdropClick }, index.h("div", { class: "dialog", role: "dialog", "aria-modal": "true", "aria-label": this.heading }, index.h("header", { class: "head" }, index.h("h2", { class: "heading" }, this.heading), index.h("button", { type: "button", class: "close", onClick: () => this.rpClose.emit(), "aria-label": "Close dialog" }, index.h("svg", { viewBox: "0 0 24 24", width: "15", height: "15", "aria-hidden": "true" }, index.h("path", { d: "M6 6l12 12M18 6L6 18" })))), index.h("div", { class: "body" }, index.h("slot", null)), index.h("footer", { class: "foot" }, index.h("slot", { name: "footer" }))))));
139
139
  }
140
140
  static get watchers() { return {
141
141
  "open": [{
@@ -145,7 +145,7 @@ const Modal = class {
145
145
  };
146
146
  Modal.style = rpModalCss();
147
147
 
148
- const rpRecipeCardCss = () => `.sc-rp-recipe-card-h{display:block;font-family:var(--rp-font-sans);color:var(--rp-color-text)}.card.sc-rp-recipe-card{display:flex;flex-direction:column;height:100%;overflow:hidden;background:var(--rp-color-surface);border:1px solid var(--rp-color-border);border-radius:var(--rp-radius-lg);box-shadow:var(--rp-shadow-sm)}.media.sc-rp-recipe-card{position:relative;aspect-ratio:4 / 3;background:var(--rp-color-surface-sunken)}.media.sc-rp-recipe-card img.sc-rp-recipe-card{display:block;width:100%;height:100%;object-fit:cover}.media-fallback.sc-rp-recipe-card{width:100%;height:100%;background:linear-gradient(135deg, var(--rp-color-surface-sunken), var(--rp-color-border))}.badges.sc-rp-recipe-card{position:absolute;inset-block-start:var(--rp-space-2);inset-inline-start:var(--rp-space-2);display:flex;flex-wrap:wrap;gap:var(--rp-space-1)}.favorite.sc-rp-recipe-card{position:absolute;inset-block-start:var(--rp-space-2);inset-inline-end:var(--rp-space-2);display:grid;place-items:center;width:36px;height:36px;padding:0;cursor:pointer;background:var(--rp-color-surface);border:1px solid var(--rp-color-border);border-radius:50%;box-shadow:var(--rp-shadow-sm)}.favorite.sc-rp-recipe-card svg.sc-rp-recipe-card{fill:none;stroke:var(--rp-color-text-muted);stroke-width:1.8}[favorite].sc-rp-recipe-card-h .favorite.sc-rp-recipe-card svg.sc-rp-recipe-card{fill:var(--rp-color-favorite);stroke:var(--rp-color-favorite)}.favorite.sc-rp-recipe-card:focus-visible{outline:var(--rp-focus-ring);outline-offset:var(--rp-focus-offset)}.body.sc-rp-recipe-card{flex:1;padding:var(--rp-space-3) var(--rp-space-4)}.title.sc-rp-recipe-card{margin:0;font-size:var(--rp-font-size-lg);line-height:1.3}.category.sc-rp-recipe-card{margin:var(--rp-space-1) 0 0;font-size:var(--rp-font-size-sm);color:var(--rp-color-text-muted)}.actions.sc-rp-recipe-card{display:flex;gap:var(--rp-space-2);align-items:center;padding:0 var(--rp-space-4) var(--rp-space-4)}.actions.sc-rp-recipe-card:not(:has(*)){display:none}`;
148
+ const rpRecipeCardCss = () => `.sc-rp-recipe-card-h{display:block;font-family:var(--rp-font-sans);color:var(--rp-color-text)}.card.sc-rp-recipe-card{display:flex;flex-direction:column;height:100%;overflow:hidden;background:var(--rp-color-surface);border:1px solid var(--rp-color-border);border-radius:var(--rp-radius-lg);box-shadow:var(--rp-shadow-sm);transition:transform var(--rp-duration) var(--rp-ease), box-shadow var(--rp-duration) var(--rp-ease), border-color var(--rp-duration) var(--rp-ease)}.sc-rp-recipe-card-h:hover .card.sc-rp-recipe-card{border-color:var(--rp-color-border-strong);box-shadow:var(--rp-shadow-lg);transform:translateY(-4px)}.media.sc-rp-recipe-card{position:relative;aspect-ratio:4 / 3;overflow:hidden;background:var(--rp-color-surface-sunken)}.media.sc-rp-recipe-card img.sc-rp-recipe-card{display:block;width:100%;height:100%;object-fit:cover;transition:transform var(--rp-duration-slow) var(--rp-ease), opacity var(--rp-duration) var(--rp-ease)}.sc-rp-recipe-card-h:hover .media.sc-rp-recipe-card img.sc-rp-recipe-card{transform:scale(1.04)}.media.is-loading.sc-rp-recipe-card img.sc-rp-recipe-card{opacity:0}.media-fallback.sc-rp-recipe-card{width:100%;height:100%;background:radial-gradient(circle at 30% 25%, rgb(255 255 255 / 0.55), transparent 55%), linear-gradient(135deg, var(--rp-caramel-100), var(--rp-cream-400))}.scrim.sc-rp-recipe-card{position:absolute;inset:0;pointer-events:none;background:linear-gradient(to bottom, rgb(44 24 16 / 0.18) 0%, transparent 34%)}.badges.sc-rp-recipe-card{position:absolute;inset-block-start:var(--rp-space-3);inset-inline-start:var(--rp-space-3);display:flex;flex-wrap:wrap;gap:var(--rp-space-1)}.favorite.sc-rp-recipe-card{position:absolute;inset-block-start:var(--rp-space-3);inset-inline-end:var(--rp-space-3);display:grid;place-items:center;width:36px;height:36px;padding:0;cursor:pointer;background:rgb(255 255 255 / 0.82);backdrop-filter:blur(8px);border:1px solid rgb(255 255 255 / 0.6);border-radius:50%;box-shadow:var(--rp-shadow-sm);transition:transform var(--rp-duration-fast) var(--rp-ease), background-color var(--rp-duration-fast) var(--rp-ease)}.favorite.sc-rp-recipe-card:hover{background:var(--rp-color-surface);transform:scale(1.08)}.favorite.sc-rp-recipe-card:active{transform:scale(0.94)}.favorite.sc-rp-recipe-card svg.sc-rp-recipe-card{fill:none;stroke:var(--rp-espresso-800);stroke-width:1.9;stroke-linejoin:round;transition:fill var(--rp-duration-fast) var(--rp-ease), stroke var(--rp-duration-fast) var(--rp-ease)}[favorite].sc-rp-recipe-card-h .favorite.sc-rp-recipe-card svg.sc-rp-recipe-card{fill:var(--rp-color-favorite);stroke:var(--rp-color-favorite)}.favorite.sc-rp-recipe-card:focus-visible{outline:var(--rp-focus-ring);outline-offset:var(--rp-focus-offset)}.body.sc-rp-recipe-card{flex:1;padding:var(--rp-space-4) var(--rp-space-4) var(--rp-space-3)}.title.sc-rp-recipe-card{margin:0;font-family:var(--rp-font-serif);font-size:1.0625rem;font-weight:600;line-height:1.28;letter-spacing:-0.01em;color:var(--rp-color-text);display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;overflow:hidden}.meta.sc-rp-recipe-card{display:flex;flex-wrap:wrap;gap:var(--rp-space-1);margin-top:var(--rp-space-3)}.pill.sc-rp-recipe-card{padding:3px 10px;font-size:var(--rp-font-size-xs);font-weight:500;letter-spacing:0.01em;color:var(--rp-color-text-muted);background:var(--rp-color-surface-sunken);border:1px solid var(--rp-color-border);border-radius:var(--rp-radius-pill);white-space:nowrap}.pill-category.sc-rp-recipe-card{color:var(--rp-caramel-600);background:var(--rp-color-highlight-soft);border-color:transparent}.actions.sc-rp-recipe-card{display:flex;gap:var(--rp-space-3);align-items:center;padding:0 var(--rp-space-4) var(--rp-space-4)}.actions.sc-rp-recipe-card:not(:has(*)){display:none}@media (prefers-reduced-motion: no-preference){.media.is-loading.sc-rp-recipe-card::after{content:'';position:absolute;inset:0;background:linear-gradient( 100deg, transparent 20%, rgb(255 255 255 / 0.7) 50%, transparent 80% );background-size:220% 100%;animation:rp-card-shimmer 1.6s var(--rp-ease) infinite}@keyframes rp-card-shimmer{from{background-position:180% 0}to{background-position:-80% 0}}.favorite.is-pulsing.sc-rp-recipe-card{animation:rp-favorite-pop 400ms var(--rp-ease)}@keyframes rp-favorite-pop{0%{transform:scale(1)}35%{transform:scale(0.86)}70%{transform:scale(1.16)}100%{transform:scale(1)}}}@media (prefers-reduced-motion: reduce){.card.sc-rp-recipe-card,.media.sc-rp-recipe-card img.sc-rp-recipe-card,.favorite.sc-rp-recipe-card{transition:none}.sc-rp-recipe-card-h:hover .card.sc-rp-recipe-card{transform:none}.sc-rp-recipe-card-h:hover .media.sc-rp-recipe-card img.sc-rp-recipe-card{transform:none}.media.is-loading.sc-rp-recipe-card img.sc-rp-recipe-card{opacity:1}}`;
149
149
 
150
150
  const RecipeCard = class {
151
151
  constructor(hostRef) {
@@ -166,22 +166,59 @@ const RecipeCard = class {
166
166
  image;
167
167
  /** Category label shown under the heading. */
168
168
  category;
169
+ /** Cuisine or region, shown as a second meta pill when present. */
170
+ area;
171
+ /** Preparation time in minutes, shown as a meta pill when present. */
172
+ minutes;
169
173
  /** Whether this recipe is currently a favorite. Reflected for CSS hooks. */
170
174
  favorite = false;
175
+ /**
176
+ * True between the image element being created and its load event.
177
+ *
178
+ * Drives the shimmer placeholder behind the image. Tracked in state rather than with a
179
+ * CSS-only trick because a cached image can complete before this component ever renders,
180
+ * and the load event would then never fire — `onLoad` plus the `complete` check in
181
+ * `onImageRef` covers both orderings.
182
+ */
183
+ imageLoading = true;
184
+ /** Set when the image URL fails, so a broken link falls back to the gradient. */
185
+ imageFailed = false;
186
+ /** Briefly true after a favorite toggle, driving the press animation. */
187
+ pulsing = false;
188
+ pulseTimer;
171
189
  /** Fired when the favorite control is activated. */
172
190
  rpFavoriteToggle;
191
+ disconnectedCallback() {
192
+ clearTimeout(this.pulseTimer);
193
+ }
173
194
  toggleFavorite = () => {
195
+ this.pulsing = true;
196
+ clearTimeout(this.pulseTimer);
197
+ this.pulseTimer = setTimeout(() => (this.pulsing = false), 400);
174
198
  // Emits the intended next state. The consumer owns the data and decides whether
175
199
  // the change is applied, so this component never writes to its own prop.
176
200
  this.rpFavoriteToggle.emit({ recipeId: this.recipeId, favorite: !this.favorite });
177
201
  };
202
+ onImageRef = (element) => {
203
+ // A browser-cached image is already complete by the time the ref runs, and its load
204
+ // event fired before the listener was attached.
205
+ if (element?.complete)
206
+ this.imageLoading = false;
207
+ };
178
208
  render() {
179
- return (index.h(index.Host, { key: '7ac4bd65a2fa94815db81c986d17e3dff768334f' }, index.h("article", { key: 'b8ab03b01a7305ebac8fa8c7a512cf670afbd7c9', class: "card" }, index.h("div", { key: 'ddd9892bf42ed7fdd1300d439449ac4c471f11e3', class: "media" }, this.image ? (index.h("img", { src: this.image, alt: "", loading: "lazy", width: "320", height: "240" })) : (index.h("div", { class: "media-fallback", "aria-hidden": "true" })), index.h("div", { key: '211b2f1621e96b2350ff17598094790809c6fe81', class: "badges" }, index.h("slot", { key: 'ab834b8c4ff5c643c35d4f29c7bc9ddab5886e44' })), index.h("button", { key: '67d76da1db3b7d867d64042260cb69b30ea9fa2f', type: "button", class: "favorite", onClick: this.toggleFavorite, "aria-pressed": String(this.favorite), "aria-label": this.favorite ? `Remove ${this.recipeTitle} from favorites` : `Add ${this.recipeTitle} to favorites` }, index.h("svg", { key: 'ad94f74c83aba86af030d380d02ca331bf1edd66', viewBox: "0 0 24 24", width: "20", height: "20", "aria-hidden": "true" }, index.h("path", { key: 'cec837ac8a9e440730ed83f5746fba9833722d43', d: "M12 21s-7.5-4.7-9.3-9A5.2 5.2 0 0 1 12 6.5 5.2 5.2 0 0 1 21.3 12c-1.8 4.3-9.3 9-9.3 9z" })))), index.h("div", { key: '57e619602fb97b1197d1391e1bca37059fad4750', class: "body" }, index.h("h3", { key: '1576688c8d62e883132bb25f7f4f0832e7e102c2', class: "title" }, this.recipeTitle), this.category && index.h("p", { key: 'ca58ac4811bf94d13761ac0fd3fdd63a4b35c057', class: "category" }, this.category)), index.h("div", { key: 'fe7531c32eb5b110fe5aea2c4680a020848633bd', class: "actions" }, index.h("slot", { key: 'daf97a1324f3d2d08d1900d057cab1968be8c9d7', name: "actions" })))));
209
+ // Normalized because a consumer that omits the attribute entirely leaves the prop
210
+ // undefined, and `aria-pressed="undefined"` would be dropped from the DOM, leaving a
211
+ // toggle button with no announced state.
212
+ const isFavorite = this.favorite === true;
213
+ const showImage = this.image && !this.imageFailed;
214
+ return (index.h(index.Host, { key: '83f0c244c604277e29c6847a7841e3ddae41b050' }, index.h("article", { key: '7243fc16bee1c4c8ba78feeb658fc8138e9c799d', class: "card" }, index.h("div", { key: '67babe6087e8acaa52379a7749f4ea6001265539', class: { media: true, 'is-loading': showImage && this.imageLoading } }, showImage ? (index.h("img", { src: this.image, alt: "", loading: "lazy", width: "320", height: "240", ref: this.onImageRef, onLoad: () => (this.imageLoading = false), onError: () => (this.imageFailed = true) })) : (index.h("div", { class: "media-fallback", "aria-hidden": "true" })), index.h("div", { key: '7f3dd8af818e08c1c8528b2cb474caf7f045651d', class: "scrim", "aria-hidden": "true" }), index.h("div", { key: 'f3e6442a93cefdcbbe20a2e44026370ca6e5f9a6', class: "badges" }, index.h("slot", { key: '18351d8d6f05b507d754fe2760118f51d6ab7a2b' })), index.h("button", { key: 'c574fc6e66243f30c75f0ff618e27025a2c9dfd6', type: "button", class: { favorite: true, 'is-pulsing': this.pulsing }, onClick: this.toggleFavorite, "aria-pressed": isFavorite ? 'true' : 'false', "aria-label": isFavorite
215
+ ? `Remove ${this.recipeTitle} from favorites`
216
+ : `Add ${this.recipeTitle} to favorites` }, index.h("svg", { key: '54ed86ab35d9afb25b21fe50c036ef7cb3a8d92a', viewBox: "0 0 24 24", width: "18", height: "18", "aria-hidden": "true" }, index.h("path", { key: '4550aaf0e5cc38fc650014ae6c9cc5f7b0c4ec40', d: "M12 21s-7.5-4.7-9.3-9A5.2 5.2 0 0 1 12 6.5 5.2 5.2 0 0 1 21.3 12c-1.8 4.3-9.3 9-9.3 9z" })))), index.h("div", { key: '20710609101f93f2e3fda399e3c8fc6cab59531b', class: "body" }, index.h("h3", { key: 'dbd67d2f6b1645a90f3b5071dedf7196c57f14b0', class: "title" }, this.recipeTitle), index.h("div", { key: 'a48a0b6c1d60fa24bcb191857d8f04d76906bc78', class: "meta" }, this.category && index.h("span", { key: '6a4c805c85ac8fddb8e316ceca3d99148df78f47', class: "pill pill-category" }, this.category), this.area && index.h("span", { key: '0e69805236fff32521dbd983889f07f6b138b629', class: "pill" }, this.area), this.minutes ? index.h("span", { class: "pill" }, this.minutes, " min") : null)), index.h("div", { key: '362d3f73ec32326cc50e3fdec7977237b9cfd1c8', class: "actions" }, index.h("slot", { key: '6822a6ad44f0414afc97c6b802f0cec276e217d7', name: "actions" })))));
180
217
  }
181
218
  };
182
219
  RecipeCard.style = rpRecipeCardCss();
183
220
 
184
- const rpSearchBarCss = () => `.sc-rp-search-bar-h{display:block;font-family:var(--rp-font-sans)}.bar.sc-rp-search-bar{display:flex;gap:var(--rp-space-2);align-items:center}.field.sc-rp-search-bar{flex:1;min-width:0;padding:var(--rp-space-2) var(--rp-space-3);font:inherit;font-size:var(--rp-font-size-md);color:var(--rp-color-text);background:var(--rp-color-surface);border:1px solid var(--rp-color-border);border-radius:var(--rp-radius-md)}.field.sc-rp-search-bar::-webkit-search-cancel-button{display:none}.field.sc-rp-search-bar:focus-visible,.submit.sc-rp-search-bar:focus-visible,.clear.sc-rp-search-bar:focus-visible{outline:var(--rp-focus-ring);outline-offset:var(--rp-focus-offset)}.clear.sc-rp-search-bar{display:grid;place-items:center;width:32px;height:32px;padding:0;font-size:1.25rem;line-height:1;color:var(--rp-color-text-muted);cursor:pointer;background:none;border:none;border-radius:50%}.clear.sc-rp-search-bar:hover{background:var(--rp-color-surface-sunken)}.submit.sc-rp-search-bar{padding:var(--rp-space-2) var(--rp-space-4);font:inherit;font-size:var(--rp-font-size-md);font-weight:600;color:var(--rp-color-accent-contrast);cursor:pointer;background:var(--rp-color-accent);border:none;border-radius:var(--rp-radius-md)}`;
221
+ const rpSearchBarCss = () => `.sc-rp-search-bar-h{display:block;font-family:var(--rp-font-sans)}.bar.sc-rp-search-bar{display:flex;gap:var(--rp-space-2);align-items:center;padding:5px 5px 5px var(--rp-space-4);background:var(--rp-color-surface);border:1px solid var(--rp-color-border);border-radius:var(--rp-radius-pill);box-shadow:var(--rp-shadow-sm);transition:border-color var(--rp-duration) var(--rp-ease), box-shadow var(--rp-duration) var(--rp-ease)}.bar.sc-rp-search-bar:hover{border-color:var(--rp-color-border-strong)}.bar.sc-rp-search-bar:focus-within{border-color:var(--rp-color-focus);box-shadow:var(--rp-shadow-sm), var(--rp-focus-halo)}.icon.sc-rp-search-bar{flex-shrink:0;fill:none;stroke:var(--rp-color-text-subtle);stroke-width:2;stroke-linecap:round;transition:stroke var(--rp-duration) var(--rp-ease)}.bar.sc-rp-search-bar:focus-within .icon.sc-rp-search-bar{stroke:var(--rp-color-focus)}.field.sc-rp-search-bar{flex:1;min-width:0;padding:var(--rp-space-2) 0;font:inherit;font-size:var(--rp-font-size-md);color:var(--rp-color-text);background:none;border:none}.field.sc-rp-search-bar::placeholder{color:var(--rp-color-text-subtle)}.field.sc-rp-search-bar:focus{outline:none}.field.sc-rp-search-bar::-webkit-search-cancel-button{display:none}.clear.sc-rp-search-bar{display:grid;place-items:center;width:30px;height:30px;padding:0;color:var(--rp-color-text-subtle);cursor:pointer;background:none;border:none;border-radius:50%;transition:color var(--rp-duration-fast) var(--rp-ease), background-color var(--rp-duration-fast) var(--rp-ease)}.clear.sc-rp-search-bar svg.sc-rp-search-bar{fill:none;stroke:currentColor;stroke-width:2.2;stroke-linecap:round}.clear.sc-rp-search-bar:hover{color:var(--rp-color-text);background:var(--rp-color-surface-sunken)}.submit.sc-rp-search-bar{flex-shrink:0;padding:var(--rp-space-2) var(--rp-space-5);font:inherit;font-size:var(--rp-font-size-md);font-weight:600;color:var(--rp-color-accent-contrast);cursor:pointer;background:var(--rp-color-accent);border:none;border-radius:var(--rp-radius-pill);transition:background-color var(--rp-duration-fast) var(--rp-ease), transform var(--rp-duration-fast) var(--rp-ease)}.submit.sc-rp-search-bar:hover{background:var(--rp-color-accent-hover)}.submit.sc-rp-search-bar:active{transform:scale(0.97)}.field.sc-rp-search-bar:focus-visible,.submit.sc-rp-search-bar:focus-visible,.clear.sc-rp-search-bar:focus-visible{outline:var(--rp-focus-ring);outline-offset:var(--rp-focus-offset)}@media (max-width: 480px){.submit.sc-rp-search-bar{padding-inline:var(--rp-space-4)}}@media (prefers-reduced-motion: reduce){.bar.sc-rp-search-bar,.icon.sc-rp-search-bar,.clear.sc-rp-search-bar,.submit.sc-rp-search-bar{transition:none}.submit.sc-rp-search-bar:active{transform:none}}`;
185
222
 
186
223
  const SearchBar = class {
187
224
  constructor(hostRef) {
@@ -223,7 +260,7 @@ const SearchBar = class {
223
260
  this.rpClear.emit();
224
261
  };
225
262
  render() {
226
- return (index.h(index.Host, { key: '12550f4e6587473576e211fe1cfa5d075a02399f' }, index.h("form", { key: 'e1a61e15c159e8449770b7df63a649d84b136aa2', class: "bar", role: "search", onSubmit: this.onSubmit }, index.h("input", { key: 'defae81130bf361beae8a57c15633c6595437493', type: "search", class: "field", value: this.draft, placeholder: this.placeholder, "aria-label": this.label, onInput: this.onInput }), this.draft && (index.h("button", { key: '4cd2eac81b32f17d18e89c51a90e0a0d9edf7d01', type: "button", class: "clear", onClick: this.onClear, "aria-label": "Clear search" }, "\u00D7")), index.h("button", { key: 'b07034ed131233623d8edbdd16db3cfd8ffab4b7', type: "submit", class: "submit" }, "Search"))));
263
+ return (index.h(index.Host, { key: '8b9328563d52d5cc0ae412bb5c3d513385705467' }, index.h("form", { key: 'a05c8de20f34fb4411ce22d04685842e38bebb93', class: "bar", role: "search", onSubmit: this.onSubmit }, index.h("svg", { key: '486ce7975b594f929c254b4bcbaf366ff35da2a5', class: "icon", viewBox: "0 0 24 24", width: "18", height: "18", "aria-hidden": "true" }, index.h("circle", { key: 'a3f1a9aa7e6b3309ef69273210a19b831ccfcfee', cx: "11", cy: "11", r: "7" }), index.h("path", { key: 'a4ebc02325f3ff0f24dfaca637a54e305be260ba', d: "m20 20-3.6-3.6" })), index.h("input", { key: '62361e4ae2ceaf13156d9feb6e3e8f5021badb86', type: "search", class: "field", value: this.draft, placeholder: this.placeholder, "aria-label": this.label, onInput: this.onInput }), this.draft && (index.h("button", { key: '81f67cb9edbdf6f5040fdabbb1d355b38d51ff9e', type: "button", class: "clear", onClick: this.onClear, "aria-label": "Clear search" }, index.h("svg", { key: 'de3195194e0a32075ba1f2e1e506d3409fd177fb', viewBox: "0 0 24 24", width: "14", height: "14", "aria-hidden": "true" }, index.h("path", { key: '03510f9ce6e1e2d7a27b03f72674819437f6d903', d: "M6 6l12 12M18 6L6 18" })))), index.h("button", { key: '17430f0dc661d72613a7a177488eab949ff561a0', type: "submit", class: "submit" }, "Search"))));
227
264
  }
228
265
  static get watchers() { return {
229
266
  "value": [{