@ssejal8/recipe-ui-kit 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/README.md +108 -277
  2. package/dist/cjs/index-BEr8BsSA.js +1712 -0
  3. package/dist/cjs/index-BEr8BsSA.js.map +1 -0
  4. package/dist/cjs/index.cjs.js +5 -0
  5. package/dist/cjs/index.cjs.js.map +1 -0
  6. package/dist/cjs/loader.cjs.js +15 -0
  7. package/dist/cjs/loader.cjs.js.map +1 -0
  8. package/dist/cjs/recipe-kit.cjs.js +27 -0
  9. package/dist/cjs/recipe-kit.cjs.js.map +1 -0
  10. package/dist/cjs/rk-badge_10.cjs.entry.js +638 -0
  11. package/dist/cjs/rk-badge_10.cjs.entry.js.map +1 -0
  12. package/dist/collection/collection-manifest.json +22 -0
  13. package/dist/collection/components/rk-badge/rk-badge.js +97 -0
  14. package/dist/collection/components/rk-badge/rk-badge.js.map +1 -0
  15. package/dist/collection/components/rk-chip/rk-chip.js +245 -0
  16. package/dist/collection/components/rk-chip/rk-chip.js.map +1 -0
  17. package/dist/collection/components/rk-empty-state/rk-empty-state.js +138 -0
  18. package/dist/collection/components/rk-empty-state/rk-empty-state.js.map +1 -0
  19. package/dist/collection/components/rk-meal-slot/rk-meal-slot.js +312 -0
  20. package/dist/collection/components/rk-meal-slot/rk-meal-slot.js.map +1 -0
  21. package/dist/collection/components/rk-modal/rk-modal.js +256 -0
  22. package/dist/collection/components/rk-modal/rk-modal.js.map +1 -0
  23. package/dist/collection/components/rk-rating/rk-rating.js +228 -0
  24. package/dist/collection/components/rk-rating/rk-rating.js.map +1 -0
  25. package/dist/collection/components/rk-recipe-card/rk-recipe-card.js +366 -0
  26. package/dist/collection/components/rk-recipe-card/rk-recipe-card.js.map +1 -0
  27. package/dist/collection/components/rk-search-bar/rk-search-bar.js +291 -0
  28. package/dist/collection/components/rk-search-bar/rk-search-bar.js.map +1 -0
  29. package/dist/collection/components/rk-segmented-control/rk-segmented-control.js +196 -0
  30. package/dist/collection/components/rk-segmented-control/rk-segmented-control.js.map +1 -0
  31. package/dist/collection/components/rk-tag-input/rk-tag-input.js +264 -0
  32. package/dist/collection/components/rk-tag-input/rk-tag-input.js.map +1 -0
  33. package/dist/collection/index.js +2 -0
  34. package/dist/collection/index.js.map +1 -0
  35. package/dist/components/index.d.ts +35 -0
  36. package/dist/components/index.js +2 -0
  37. package/dist/components/index.js.map +1 -0
  38. package/dist/components/rk-badge.d.ts +11 -0
  39. package/dist/components/rk-badge.js +2 -0
  40. package/dist/components/rk-badge.js.map +1 -0
  41. package/dist/components/rk-chip.d.ts +11 -0
  42. package/dist/components/rk-chip.js +2 -0
  43. package/dist/components/rk-chip.js.map +1 -0
  44. package/dist/components/rk-empty-state.d.ts +11 -0
  45. package/dist/components/rk-empty-state.js +2 -0
  46. package/dist/components/rk-empty-state.js.map +1 -0
  47. package/dist/components/rk-meal-slot.d.ts +11 -0
  48. package/dist/components/rk-meal-slot.js +2 -0
  49. package/dist/components/rk-meal-slot.js.map +1 -0
  50. package/dist/components/rk-modal.d.ts +11 -0
  51. package/dist/components/rk-modal.js +2 -0
  52. package/dist/components/rk-modal.js.map +1 -0
  53. package/dist/components/rk-rating.d.ts +11 -0
  54. package/dist/components/rk-rating.js +2 -0
  55. package/dist/components/rk-rating.js.map +1 -0
  56. package/dist/components/rk-recipe-card.d.ts +11 -0
  57. package/dist/components/rk-recipe-card.js +2 -0
  58. package/dist/components/rk-recipe-card.js.map +1 -0
  59. package/dist/components/rk-search-bar.d.ts +11 -0
  60. package/dist/components/rk-search-bar.js +2 -0
  61. package/dist/components/rk-search-bar.js.map +1 -0
  62. package/dist/components/rk-segmented-control.d.ts +11 -0
  63. package/dist/components/rk-segmented-control.js +2 -0
  64. package/dist/components/rk-segmented-control.js.map +1 -0
  65. package/dist/components/rk-tag-input.d.ts +11 -0
  66. package/dist/components/rk-tag-input.js +2 -0
  67. package/dist/components/rk-tag-input.js.map +1 -0
  68. package/dist/esm/index-Bxo0C8lB.js +1684 -0
  69. package/dist/esm/index-Bxo0C8lB.js.map +1 -0
  70. package/dist/esm/index.js +4 -0
  71. package/dist/esm/index.js.map +1 -0
  72. package/dist/esm/loader.js +13 -0
  73. package/dist/esm/loader.js.map +1 -0
  74. package/dist/esm/recipe-kit.js +23 -0
  75. package/dist/esm/recipe-kit.js.map +1 -0
  76. package/dist/esm/rk-badge_10.entry.js +627 -0
  77. package/dist/esm/rk-badge_10.entry.js.map +1 -0
  78. package/dist/index.cjs.js +1 -0
  79. package/dist/index.js +1 -0
  80. package/dist/recipe-kit/index.esm.js +2 -0
  81. package/dist/recipe-kit/index.esm.js.map +1 -0
  82. package/dist/recipe-kit/p-Bxo0C8lB.js +3 -0
  83. package/dist/recipe-kit/p-Bxo0C8lB.js.map +1 -0
  84. package/dist/recipe-kit/p-fcb955c7.entry.js +2 -0
  85. package/dist/recipe-kit/p-fcb955c7.entry.js.map +1 -0
  86. package/dist/recipe-kit/recipe-kit.css +1 -0
  87. package/dist/recipe-kit/recipe-kit.esm.js +2 -0
  88. package/dist/recipe-kit/recipe-kit.esm.js.map +1 -0
  89. package/dist/recipe-kit/rk-global.css +104 -0
  90. package/dist/types/components/rk-badge/rk-badge.d.ts +15 -0
  91. package/dist/types/components/rk-chip/rk-chip.d.ts +37 -0
  92. package/dist/types/components/rk-empty-state/rk-empty-state.d.ts +19 -0
  93. package/dist/types/components/rk-meal-slot/rk-meal-slot.d.ts +53 -0
  94. package/dist/types/components/rk-modal/rk-modal.d.ts +41 -0
  95. package/dist/types/components/rk-rating/rk-rating.d.ts +26 -0
  96. package/dist/types/components/rk-recipe-card/rk-recipe-card.d.ts +67 -0
  97. package/dist/types/components/rk-search-bar/rk-search-bar.d.ts +39 -0
  98. package/dist/types/components/rk-segmented-control/rk-segmented-control.d.ts +37 -0
  99. package/dist/types/components/rk-tag-input/rk-tag-input.d.ts +35 -0
  100. package/{packages/recipe-kit-stencil/src → dist/types}/components.d.ts +1 -1
  101. package/{packages/recipe-kit-stencil/src/index.ts → dist/types/index.d.ts} +0 -2
  102. package/dist/types/stencil-public-runtime.d.ts +1873 -0
  103. package/loader/cdn.js +1 -0
  104. package/loader/index.cjs.js +1 -0
  105. package/loader/index.d.ts +24 -0
  106. package/loader/index.es2017.js +1 -0
  107. package/loader/index.js +2 -0
  108. package/package.json +84 -22
  109. package/.claude/settings.local.json +0 -10
  110. package/.npmr +0 -1
  111. package/apps/recipe-web/package.json +0 -31
  112. package/apps/recipe-web/src/app.css +0 -418
  113. package/apps/recipe-web/src/app.d.ts +0 -32
  114. package/apps/recipe-web/src/app.html +0 -25
  115. package/apps/recipe-web/src/lib/api/mealdb.ts +0 -240
  116. package/apps/recipe-web/src/lib/components/AppHeader.svelte +0 -290
  117. package/apps/recipe-web/src/lib/components/ConfirmDialog.svelte +0 -46
  118. package/apps/recipe-web/src/lib/components/Modal.svelte +0 -33
  119. package/apps/recipe-web/src/lib/components/RecipeCard.svelte +0 -100
  120. package/apps/recipe-web/src/lib/components/RecipeForm.svelte +0 -560
  121. package/apps/recipe-web/src/lib/components/RecipeGrid.svelte +0 -77
  122. package/apps/recipe-web/src/lib/components/RecipePickerDialog.svelte +0 -185
  123. package/apps/recipe-web/src/lib/components/Toaster.svelte +0 -111
  124. package/apps/recipe-web/src/lib/domain/recipe.ts +0 -130
  125. package/apps/recipe-web/src/lib/domain/validation.ts +0 -189
  126. package/apps/recipe-web/src/lib/stencil/WebComponent.svelte +0 -145
  127. package/apps/recipe-web/src/lib/stencil/register.ts +0 -28
  128. package/apps/recipe-web/src/lib/stores/favorites.svelte.ts +0 -81
  129. package/apps/recipe-web/src/lib/stores/myRecipes.svelte.ts +0 -98
  130. package/apps/recipe-web/src/lib/stores/persisted.svelte.ts +0 -73
  131. package/apps/recipe-web/src/lib/stores/planner.svelte.ts +0 -275
  132. package/apps/recipe-web/src/lib/stores/theme.svelte.ts +0 -67
  133. package/apps/recipe-web/src/lib/stores/toast.svelte.ts +0 -66
  134. package/apps/recipe-web/src/routes/+error.svelte +0 -36
  135. package/apps/recipe-web/src/routes/+layout.svelte +0 -62
  136. package/apps/recipe-web/src/routes/+page.svelte +0 -352
  137. package/apps/recipe-web/src/routes/+page.ts +0 -107
  138. package/apps/recipe-web/src/routes/favorites/+page.svelte +0 -170
  139. package/apps/recipe-web/src/routes/my-recipes/+layout.ts +0 -55
  140. package/apps/recipe-web/src/routes/my-recipes/+page.svelte +0 -180
  141. package/apps/recipe-web/src/routes/my-recipes/[id]/edit/+page.svelte +0 -104
  142. package/apps/recipe-web/src/routes/my-recipes/new/+page.svelte +0 -67
  143. package/apps/recipe-web/src/routes/planner/+page.svelte +0 -422
  144. package/apps/recipe-web/src/routes/recipes/[id]/+page.svelte +0 -538
  145. package/apps/recipe-web/src/routes/recipes/[id]/+page.ts +0 -24
  146. package/apps/recipe-web/static/favicon.svg +0 -8
  147. package/apps/recipe-web/static/robots.txt +0 -2
  148. package/apps/recipe-web/svelte.config.js +0 -35
  149. package/apps/recipe-web/tsconfig.json +0 -14
  150. package/apps/recipe-web/vite.config.ts +0 -23
  151. package/packages/recipe-kit-stencil/README.md +0 -185
  152. package/packages/recipe-kit-stencil/package-lock.json +0 -4772
  153. package/packages/recipe-kit-stencil/package.json +0 -91
  154. package/packages/recipe-kit-stencil/src/components/rk-badge/readme.md +0 -38
  155. package/packages/recipe-kit-stencil/src/components/rk-badge/rk-badge.tsx +0 -31
  156. package/packages/recipe-kit-stencil/src/components/rk-chip/readme.md +0 -52
  157. package/packages/recipe-kit-stencil/src/components/rk-chip/rk-chip.tsx +0 -105
  158. package/packages/recipe-kit-stencil/src/components/rk-empty-state/readme.md +0 -44
  159. package/packages/recipe-kit-stencil/src/components/rk-empty-state/rk-empty-state.tsx +0 -50
  160. package/packages/recipe-kit-stencil/src/components/rk-meal-slot/readme.md +0 -58
  161. package/packages/recipe-kit-stencil/src/components/rk-meal-slot/rk-meal-slot.tsx +0 -182
  162. package/packages/recipe-kit-stencil/src/components/rk-modal/readme.md +0 -57
  163. package/packages/recipe-kit-stencil/src/components/rk-modal/rk-modal.tsx +0 -176
  164. package/packages/recipe-kit-stencil/src/components/rk-rating/readme.md +0 -42
  165. package/packages/recipe-kit-stencil/src/components/rk-rating/rk-rating.tsx +0 -114
  166. package/packages/recipe-kit-stencil/src/components/rk-recipe-card/readme.md +0 -66
  167. package/packages/recipe-kit-stencil/src/components/rk-recipe-card/rk-recipe-card.tsx +0 -179
  168. package/packages/recipe-kit-stencil/src/components/rk-recipe-card/test/rk-recipe-card.spec.tsx +0 -70
  169. package/packages/recipe-kit-stencil/src/components/rk-search-bar/readme.md +0 -67
  170. package/packages/recipe-kit-stencil/src/components/rk-search-bar/rk-search-bar.tsx +0 -152
  171. package/packages/recipe-kit-stencil/src/components/rk-search-bar/test/rk-search-bar.spec.tsx +0 -93
  172. package/packages/recipe-kit-stencil/src/components/rk-segmented-control/readme.md +0 -41
  173. package/packages/recipe-kit-stencil/src/components/rk-segmented-control/rk-segmented-control.tsx +0 -115
  174. package/packages/recipe-kit-stencil/src/components/rk-tag-input/readme.md +0 -47
  175. package/packages/recipe-kit-stencil/src/components/rk-tag-input/rk-tag-input.tsx +0 -181
  176. package/packages/recipe-kit-stencil/src/components/rk-tag-input/test/rk-tag-input.spec.tsx +0 -76
  177. package/packages/recipe-kit-stencil/src/index.html +0 -259
  178. package/packages/recipe-kit-stencil/stencil.config.ts +0 -41
  179. package/packages/recipe-kit-stencil/tsconfig.json +0 -24
  180. /package/{packages/recipe-kit-stencil/LICENSE → LICENSE} +0 -0
  181. /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-badge/rk-badge.css +0 -0
  182. /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-chip/rk-chip.css +0 -0
  183. /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-empty-state/rk-empty-state.css +0 -0
  184. /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-meal-slot/rk-meal-slot.css +0 -0
  185. /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-modal/rk-modal.css +0 -0
  186. /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-rating/rk-rating.css +0 -0
  187. /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-recipe-card/rk-recipe-card.css +0 -0
  188. /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-search-bar/rk-search-bar.css +0 -0
  189. /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-segmented-control/rk-segmented-control.css +0 -0
  190. /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-tag-input/rk-tag-input.css +0 -0
  191. /package/{packages/recipe-kit-stencil/src/global → dist/collection}/rk-global.css +0 -0
@@ -0,0 +1,627 @@
1
+ import { r as registerInstance, h, c as createEvent, H as Host, a as getElement } from './index-Bxo0C8lB.js';
2
+
3
+ const rkBadgeCss = () => `:host{display:inline-flex;font-family:var(--rk-font);vertical-align:middle}.badge{display:inline-flex;align-items:center;gap:5px;padding:4px 10px;border-radius:var(--rk-radius-pill, 999px);border:1px solid transparent;font-size:0.76em;font-weight:650;line-height:1.35;letter-spacing:0.01em;white-space:nowrap}.badge.is-small{padding:2px 8px;font-size:0.7em}.variant-soft.tone-neutral{background:var(--rk-surface-2, #fdf5ef);color:var(--rk-ink-2, #52443c)}.variant-soft.tone-primary{background:var(--rk-primary-soft, #fdeee6);color:var(--rk-primary-strong, #b23c11)}.variant-soft.tone-accent{background:var(--rk-accent-soft, #e2f3f1);color:var(--rk-accent, #0f766e)}.variant-soft.tone-success{background:var(--rk-success-soft, #e6f5eb);color:var(--rk-success, #15803d)}.variant-soft.tone-warning{background:var(--rk-warning-soft, #fdf3d8);color:var(--rk-warning, #a16207)}.variant-soft.tone-danger{background:var(--rk-danger-soft, #fdeae1);color:var(--rk-danger, #c2410c)}.variant-solid{color:#fff}.variant-solid.tone-neutral{background:var(--rk-ink, #211a16)}.variant-solid.tone-primary{background:var(--rk-primary, #d94f1b)}.variant-solid.tone-accent{background:var(--rk-accent, #0f766e)}.variant-solid.tone-success{background:var(--rk-success, #15803d)}.variant-solid.tone-warning{background:var(--rk-warning, #a16207)}.variant-solid.tone-danger{background:var(--rk-danger, #c2410c)}.variant-outline{background:transparent}.variant-outline.tone-neutral{border-color:var(--rk-border-strong, #d9c6b8);color:var(--rk-ink-2, #52443c)}.variant-outline.tone-primary{border-color:var(--rk-primary, #d94f1b);color:var(--rk-primary, #d94f1b)}.variant-outline.tone-accent{border-color:var(--rk-accent, #0f766e);color:var(--rk-accent, #0f766e)}.variant-outline.tone-success{border-color:var(--rk-success, #15803d);color:var(--rk-success, #15803d)}.variant-outline.tone-warning{border-color:var(--rk-warning, #a16207);color:var(--rk-warning, #a16207)}.variant-outline.tone-danger{border-color:var(--rk-danger, #c2410c);color:var(--rk-danger, #c2410c)}`;
4
+
5
+ const RkBadge = class {
6
+ constructor(hostRef) {
7
+ registerInstance(this, hostRef);
8
+ /** Color treatment. */
9
+ this.tone = 'neutral';
10
+ /** `soft` is tinted, `solid` is filled, `outline` is bordered only. */
11
+ this.variant = 'soft';
12
+ /** Compact sizing for dense layouts. */
13
+ this.small = false;
14
+ }
15
+ render() {
16
+ return (h("span", { key: '874569517e9ef5f7eeeac3435437d7c9671f3017', class: { badge: true, [`tone-${this.tone}`]: true, [`variant-${this.variant}`]: true, 'is-small': this.small }, part: "badge" }, h("slot", { key: '8dbd64b3916c73bc377c3e4153daaf88f339e5d1' })));
17
+ }
18
+ };
19
+ RkBadge.style = rkBadgeCss();
20
+
21
+ const rkChipCss = () => `:host{display:inline-flex;font-family:var(--rk-font);vertical-align:middle}.chip,.body{display:inline-flex;align-items:center;gap:7px;font:inherit;font-size:0.88em;font-weight:550;line-height:1;color:var(--rk-ink-2, #52443c);background:var(--rk-surface, #fff);border:1px solid var(--rk-border, #ecdfd4);border-radius:var(--rk-radius-pill, 999px);padding:9px 15px;cursor:pointer;white-space:nowrap;transition:background var(--rk-transition), border-color var(--rk-transition), color var(--rk-transition), transform var(--rk-transition)}.chip:hover:not(:disabled),.body:hover:not(:disabled){border-color:var(--rk-border-strong, #d9c6b8);background:var(--rk-surface-2, #fdf5ef)}.chip:active:not(:disabled){transform:translateY(1px)}.chip:focus-visible,.body:focus-visible,.dismiss:focus-visible{outline:none;box-shadow:var(--rk-focus-ring)}.chip:disabled,.body:disabled{opacity:0.5;cursor:not-allowed}.chip.is-selected.tone-neutral{background:var(--rk-ink, #211a16);border-color:var(--rk-ink, #211a16);color:var(--rk-surface, #fff)}.chip.is-selected.tone-primary{background:var(--rk-primary, #d94f1b);border-color:var(--rk-primary, #d94f1b);color:#fff}.chip.is-selected.tone-accent{background:var(--rk-accent, #0f766e);border-color:var(--rk-accent, #0f766e);color:#fff}.chip.is-selected:hover:not(:disabled){filter:brightness(1.08)}.count{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;border-radius:var(--rk-radius-pill, 999px);background:var(--rk-surface-2, #fdf5ef);color:var(--rk-muted, #857166);font-size:0.78em;font-weight:650}.chip.is-selected .count{background:rgba(255, 255, 255, 0.22);color:inherit}.lead{display:contents}.lead ::slotted(*){font-size:1.05em;line-height:1}.chip.is-dismissible{padding:0;background:transparent;border:0;border-radius:var(--rk-radius-pill, 999px);gap:0}.chip.is-dismissible .body{border-top-right-radius:0;border-bottom-right-radius:0;border-right:0}.dismiss{display:inline-flex;align-items:center;justify-content:center;width:32px;padding:0;border:1px solid var(--rk-border, #ecdfd4);border-left:0;border-radius:0 var(--rk-radius-pill, 999px) var(--rk-radius-pill, 999px) 0;background:var(--rk-surface, #fff);color:var(--rk-muted, #857166);cursor:pointer;transition:background var(--rk-transition), color var(--rk-transition)}.dismiss:hover:not(:disabled){background:var(--rk-danger-soft, #fdeae1);color:var(--rk-danger, #c2410c)}.dismiss:disabled{opacity:0.5;cursor:not-allowed}.dismiss svg{width:12px;height:12px}@media (prefers-reduced-motion: reduce){.chip,.body,.dismiss{transition:none}}`;
22
+
23
+ const RkChip = class {
24
+ constructor(hostRef) {
25
+ registerInstance(this, hostRef);
26
+ this.rkChipToggle = createEvent(this, "rkChipToggle");
27
+ this.rkChipDismiss = createEvent(this, "rkChipDismiss");
28
+ /** Visible text. */
29
+ this.label = '';
30
+ /** Whether the chip is currently active. */
31
+ this.selected = false;
32
+ /** Renders an "x" button that emits `rkChipDismiss` instead of toggling. */
33
+ this.dismissible = false;
34
+ /** Disables interaction. */
35
+ this.disabled = false;
36
+ /** Visual treatment. */
37
+ this.tone = 'neutral';
38
+ this.handleClick = () => {
39
+ if (this.disabled)
40
+ return;
41
+ this.rkChipToggle.emit({
42
+ value: this.resolvedValue,
43
+ label: this.label,
44
+ selected: !this.selected,
45
+ });
46
+ };
47
+ this.handleDismiss = (event) => {
48
+ event.stopPropagation();
49
+ if (this.disabled)
50
+ return;
51
+ this.rkChipDismiss.emit({ value: this.resolvedValue, label: this.label });
52
+ };
53
+ }
54
+ get resolvedValue() {
55
+ return this.value ?? this.label;
56
+ }
57
+ render() {
58
+ const body = [
59
+ h("span", { class: "lead" }, h("slot", null)),
60
+ h("span", { class: "label", part: "label" }, this.label),
61
+ typeof this.count === 'number' && (h("span", { class: "count", part: "count" }, this.count)),
62
+ ];
63
+ const classes = {
64
+ chip: true,
65
+ 'is-selected': this.selected,
66
+ 'is-dismissible': this.dismissible,
67
+ [`tone-${this.tone}`]: true,
68
+ };
69
+ // A dismissible chip needs two independent controls, so the shell becomes a
70
+ // plain element to avoid nesting buttons inside a button.
71
+ if (this.dismissible) {
72
+ return (h("span", { class: classes, part: "chip" }, h("button", { class: "body", type: "button", disabled: this.disabled, onClick: this.handleClick }, body), h("button", { class: "dismiss", part: "dismiss", type: "button", disabled: this.disabled, "aria-label": `Remove ${this.label}`, onClick: this.handleDismiss }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2.4", "stroke-linecap": "round", "aria-hidden": "true" }, h("path", { d: "M6 6l12 12M18 6 6 18" })))));
73
+ }
74
+ return (h("button", { class: classes, part: "chip", type: "button", "aria-pressed": String(this.selected), disabled: this.disabled, onClick: this.handleClick }, body));
75
+ }
76
+ };
77
+ RkChip.style = rkChipCss();
78
+
79
+ const rkEmptyStateCss = () => `:host{display:block;font-family:var(--rk-font);font-size:var(--rk-font-size, 15px)}.empty{display:flex;flex-direction:column;align-items:center;text-align:center;gap:8px;padding:52px 28px;border:1.5px dashed var(--rk-border-strong, #d9c6b8);border-radius:var(--rk-radius-lg, 20px);background:var(--rk-surface-2, #fdf5ef);color:var(--rk-ink, #211a16)}.empty.is-bare{border:0;background:transparent;padding:28px 16px}.empty.tone-error{border-color:var(--rk-danger, #c2410c);background:var(--rk-danger-soft, #fdeae1)}.icon{font-size:2.5rem;line-height:1;margin-bottom:4px}.heading{margin:0;font-family:var(--rk-font-display, serif);font-size:1.25em;font-weight:650;letter-spacing:-0.01em}.message{margin:0;max-width:46ch;color:var(--rk-muted, #857166);line-height:1.55;font-size:0.94em}.empty.tone-error .message{color:var(--rk-danger, #c2410c)}.actions{display:contents}.actions ::slotted(*){margin-top:12px}`;
80
+
81
+ const RkEmptyState = class {
82
+ constructor(hostRef) {
83
+ registerInstance(this, hostRef);
84
+ /** Emoji or short glyph shown above the heading. */
85
+ this.icon = '🍳';
86
+ /** Bold headline. */
87
+ this.heading = 'Nothing here yet';
88
+ /** `error` swaps to a warning treatment. */
89
+ this.tone = 'neutral';
90
+ /** Removes the dashed border and reduces padding, for inline use. */
91
+ this.bare = false;
92
+ }
93
+ render() {
94
+ return (h("div", { key: 'd770d00625d0f8933704195e975ff96e2599a8a9', class: { empty: true, [`tone-${this.tone}`]: true, 'is-bare': this.bare }, part: "container", role: this.tone === 'error' ? 'alert' : null }, h("span", { key: '566dc86da2eaaa9765c3112f381aa409bdcf5198', class: "icon", part: "icon", "aria-hidden": "true" }, this.icon), h("h3", { key: '9d8e62cc4724b970b6c16dd51797e6c480bf38d2', class: "heading", part: "heading" }, this.heading), this.message && (h("p", { key: 'b200ce21253e8cd7f9aa73b513030656db956a5e', class: "message", part: "message" }, this.message)), h("div", { key: '1d04761bf9fa953362826e13be1fc95eebc9067a', class: "actions", part: "actions" }, h("slot", { key: 'f1302bd354850c09e6fb4b136a32759040b30ecd' }))));
95
+ }
96
+ };
97
+ RkEmptyState.style = rkEmptyStateCss();
98
+
99
+ const rkMealSlotCss = () => `:host{display:block;font-family:var(--rk-font);font-size:var(--rk-font-size, 15px);color:var(--rk-ink, #211a16)}.slot{position:relative;width:100%;box-sizing:border-box;border-radius:var(--rk-radius, 14px);transition:border-color var(--rk-transition), background var(--rk-transition), box-shadow var(--rk-transition)}.slot.is-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;min-height:86px;padding:12px 10px;border:1.5px dashed var(--rk-border-strong, #d9c6b8);background:transparent;color:var(--rk-muted, #857166);font:inherit;cursor:pointer}.slot.is-empty:hover:not(:disabled){border-color:var(--rk-primary, #d94f1b);background:var(--rk-primary-soft, #fdeee6);color:var(--rk-primary-strong, #b23c11)}.slot.is-empty:focus-visible{outline:none;box-shadow:var(--rk-focus-ring)}.slot.is-empty:disabled{opacity:0.5;cursor:not-allowed}.plus{font-size:1.3rem;line-height:1;font-weight:300}.empty-label{font-size:0.76em;font-weight:600;text-transform:uppercase;letter-spacing:0.06em}.slot.is-filled{display:flex;flex-direction:column;gap:6px;min-height:86px;padding:10px;border:1px solid var(--rk-border, #ecdfd4);background:var(--rk-surface, #fff);box-shadow:var(--rk-shadow-sm)}.caption{font-size:0.7em;font-weight:700;text-transform:uppercase;letter-spacing:0.07em;color:var(--rk-muted, #857166)}.main{display:flex;align-items:center;gap:9px;width:100%;padding:0;border:0;background:transparent;text-align:left;font:inherit;color:inherit;cursor:pointer;border-radius:var(--rk-radius-sm, 8px)}.main:hover:not(:disabled) .name{color:var(--rk-primary, #d94f1b)}.main:focus-visible{outline:none;box-shadow:var(--rk-focus-ring)}.main:disabled{cursor:not-allowed}.thumb{flex:0 0 auto;width:38px;height:38px;border-radius:var(--rk-radius-sm, 8px);object-fit:cover;background:var(--rk-surface-2, #fdf5ef)}.thumb-fallback{display:grid;place-items:center;font-size:1.1rem}.name{font-size:0.87em;font-weight:600;line-height:1.3;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;transition:color var(--rk-transition)}.extra{display:contents}.extra ::slotted(*){font-size:0.8em;color:var(--rk-muted, #857166)}.controls{position:absolute;top:6px;right:6px;display:flex;gap:3px;opacity:0;transition:opacity var(--rk-transition)}.slot.is-filled:hover .controls,.slot.is-filled:focus-within .controls{opacity:1}.ctrl{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:1px solid var(--rk-border, #ecdfd4);border-radius:50%;background:var(--rk-surface, #fff);color:var(--rk-ink-2, #52443c);cursor:pointer;box-shadow:var(--rk-shadow-sm);transition:background var(--rk-transition), color var(--rk-transition)}.ctrl:hover:not(:disabled){background:var(--rk-surface-2, #fdf5ef);color:var(--rk-primary, #d94f1b)}.ctrl.is-danger:hover:not(:disabled){background:var(--rk-danger-soft, #fdeae1);color:var(--rk-danger, #c2410c)}.ctrl:focus-visible{outline:none;box-shadow:var(--rk-focus-ring)}.ctrl:disabled{opacity:0.5;cursor:not-allowed}.ctrl svg{width:12px;height:12px}.slot.is-drag-over{border-color:var(--rk-accent, #0f766e) !important;border-style:solid;background:var(--rk-accent-soft, #e2f3f1) !important;box-shadow:0 0 0 3px rgba(15, 118, 110, 0.2)}@media (hover: none){.controls{opacity:1}}@media (prefers-reduced-motion: reduce){.slot,.controls,.ctrl,.name{transition:none}}`;
100
+
101
+ const RkMealSlot = class {
102
+ constructor(hostRef) {
103
+ registerInstance(this, hostRef);
104
+ this.rkMealAdd = createEvent(this, "rkMealAdd");
105
+ this.rkMealSwap = createEvent(this, "rkMealSwap");
106
+ this.rkMealRemove = createEvent(this, "rkMealRemove");
107
+ this.rkMealOpen = createEvent(this, "rkMealOpen");
108
+ this.rkMealDrop = createEvent(this, "rkMealDrop");
109
+ /** Disables interaction. */
110
+ this.disabled = false;
111
+ this.dragOver = false;
112
+ this.handleDragOver = (event) => {
113
+ if (this.disabled)
114
+ return;
115
+ // Preventing default is what marks this element as a valid drop target.
116
+ event.preventDefault();
117
+ if (event.dataTransfer)
118
+ event.dataTransfer.dropEffect = 'copy';
119
+ this.dragOver = true;
120
+ };
121
+ this.handleDrop = (event) => {
122
+ event.preventDefault();
123
+ this.dragOver = false;
124
+ if (this.disabled)
125
+ return;
126
+ const payload = event.dataTransfer?.getData('text/plain') ?? '';
127
+ if (payload)
128
+ this.rkMealDrop.emit({ ...this.slotDetail, payload });
129
+ };
130
+ }
131
+ get filled() {
132
+ return !!this.recipeId;
133
+ }
134
+ get slotDetail() {
135
+ return { day: this.day, mealType: this.mealType };
136
+ }
137
+ get recipeDetail() {
138
+ return { ...this.slotDetail, recipeId: this.recipeId ?? '', recipeName: this.recipeName ?? '' };
139
+ }
140
+ render() {
141
+ if (!this.filled) {
142
+ return (h("button", { class: { slot: true, 'is-empty': true, 'is-drag-over': this.dragOver }, part: "slot", type: "button", disabled: this.disabled, "aria-label": `Add a recipe for ${this.mealLabel ?? this.mealType} on ${this.day}`, onClick: () => this.rkMealAdd.emit(this.slotDetail), onDragOver: this.handleDragOver, onDragLeave: () => (this.dragOver = false), onDrop: this.handleDrop }, h("span", { class: "plus", "aria-hidden": "true" }, "+"), h("span", { class: "empty-label" }, this.mealLabel ?? this.mealType)));
143
+ }
144
+ return (h("div", { class: { slot: true, 'is-filled': true, 'is-drag-over': this.dragOver }, part: "slot", onDragOver: this.handleDragOver, onDragLeave: () => (this.dragOver = false), onDrop: this.handleDrop }, this.mealLabel && (h("span", { class: "caption", part: "caption" }, this.mealLabel)), h("button", { class: "main", part: "main", type: "button", disabled: this.disabled, title: this.recipeName, onClick: () => this.rkMealOpen.emit(this.recipeDetail) }, this.recipeImage ? (h("img", { class: "thumb", src: this.recipeImage, alt: "", loading: "lazy", decoding: "async" })) : (h("span", { class: "thumb thumb-fallback", "aria-hidden": "true" }, "\uD83C\uDF7D\uFE0F")), h("span", { class: "name", part: "name" }, this.recipeName)), h("div", { class: "extra" }, h("slot", null)), h("div", { class: "controls", part: "controls" }, h("button", { class: "ctrl", type: "button", disabled: this.disabled, "aria-label": `Replace ${this.recipeName}`, title: "Replace", onClick: () => this.rkMealSwap.emit(this.recipeDetail) }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true" }, h("path", { d: "M3 12a9 9 0 0 1 15.3-6.4L21 8" }), h("path", { d: "M21 3v5h-5" }), h("path", { d: "M21 12a9 9 0 0 1-15.3 6.4L3 16" }), h("path", { d: "M3 21v-5h5" }))), h("button", { class: "ctrl is-danger", type: "button", disabled: this.disabled, "aria-label": `Remove ${this.recipeName} from ${this.mealLabel ?? this.mealType}`, title: "Remove", onClick: () => this.rkMealRemove.emit(this.recipeDetail) }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "aria-hidden": "true" }, h("path", { d: "M6 6l12 12M18 6 6 18" }))))));
145
+ }
146
+ };
147
+ RkMealSlot.style = rkMealSlotCss();
148
+
149
+ const rkModalCss = () => `:host{font-family:var(--rk-font);font-size:var(--rk-font-size, 15px);color:var(--rk-ink, #211a16)}.backdrop{position:fixed;inset:0;z-index:var(--rk-z-modal, 1000);display:flex;align-items:center;justify-content:center;padding:20px;background:rgba(24, 17, 13, 0.5);backdrop-filter:blur(3px);animation:rk-fade-in 140ms ease-out;overflow-y:auto}.panel{display:flex;flex-direction:column;width:100%;max-height:calc(100vh - 40px);background:var(--rk-surface, #fff);border-radius:var(--rk-radius-lg, 20px);box-shadow:var(--rk-shadow-lg);overflow:hidden;animation:rk-pop-in 180ms cubic-bezier(0.2, 0, 0.2, 1)}.panel:focus{outline:none}.size-sm{max-width:420px}.size-md{max-width:620px}.size-lg{max-width:880px}.header{display:flex;align-items:flex-start;gap:16px;padding:22px 24px 16px;border-bottom:1px solid var(--rk-border, #ecdfd4);flex:0 0 auto}.titles{flex:1 1 auto;min-width:0}.heading{margin:0;font-family:var(--rk-font-display, serif);font-size:1.32em;font-weight:650;letter-spacing:-0.015em;line-height:1.25}.subheading{margin:5px 0 0;font-size:0.9em;color:var(--rk-muted, #857166);line-height:1.5}.close{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;padding:0;border:0;border-radius:50%;background:var(--rk-surface-2, #fdf5ef);color:var(--rk-ink-2, #52443c);cursor:pointer;transition:background var(--rk-transition), color var(--rk-transition)}.close:hover{background:var(--rk-danger-soft, #fdeae1);color:var(--rk-danger, #c2410c)}.close:focus-visible{outline:none;box-shadow:var(--rk-focus-ring)}.close svg{width:16px;height:16px}.body{padding:20px 24px;overflow-y:auto;flex:1 1 auto;line-height:1.6}.footer{display:flex;align-items:center;justify-content:flex-end;gap:10px;padding:16px 24px;border-top:1px solid var(--rk-border, #ecdfd4);background:var(--rk-surface-2, #fdf5ef);flex:0 0 auto}.footer.is-empty{display:none}@keyframes rk-fade-in{from{opacity:0}}@keyframes rk-pop-in{from{opacity:0;transform:translateY(12px) scale(0.98)}}@media (max-width: 560px){.backdrop{padding:0;align-items:flex-end}.panel{border-radius:var(--rk-radius-lg, 20px) var(--rk-radius-lg, 20px) 0 0;max-height:92vh;max-width:none}}@media (prefers-reduced-motion: reduce){.backdrop,.panel{animation:none}}`;
150
+
151
+ const RkModal = class {
152
+ constructor(hostRef) {
153
+ registerInstance(this, hostRef);
154
+ this.rkModalClose = createEvent(this, "rkModalClose");
155
+ /** Controls visibility. */
156
+ this.open = false;
157
+ /** Heading text for the default header. */
158
+ this.heading = '';
159
+ /** Dialog width preset. */
160
+ this.size = 'md';
161
+ /** Set to false to require an explicit action to dismiss. */
162
+ this.dismissible = true;
163
+ this.hasFooter = false;
164
+ /** Collapses the footer region when no action row was projected into it. */
165
+ this.syncFooterSlot = () => {
166
+ this.hasFooter = Array.from(this.host.children).some(node => node.getAttribute('slot') === 'footer');
167
+ };
168
+ this.handleGlobalKeyDown = (event) => {
169
+ if (!this.open)
170
+ return;
171
+ if (event.key === 'Escape' && this.dismissible) {
172
+ event.preventDefault();
173
+ this.rkModalClose.emit();
174
+ return;
175
+ }
176
+ if (event.key === 'Tab') {
177
+ const focusable = this.focusableElements();
178
+ if (focusable.length === 0)
179
+ return;
180
+ const first = focusable[0];
181
+ const last = focusable[focusable.length - 1];
182
+ const active = (this.host.shadowRoot?.activeElement ?? document.activeElement);
183
+ if (event.shiftKey && active === first) {
184
+ event.preventDefault();
185
+ last.focus();
186
+ }
187
+ else if (!event.shiftKey && active === last) {
188
+ event.preventDefault();
189
+ first.focus();
190
+ }
191
+ }
192
+ };
193
+ this.handleBackdrop = (event) => {
194
+ if (!this.dismissible)
195
+ return;
196
+ if (event.target === event.currentTarget)
197
+ this.rkModalClose.emit();
198
+ };
199
+ }
200
+ onOpenChange(isOpen, wasOpen) {
201
+ if (isOpen === wasOpen)
202
+ return;
203
+ if (isOpen) {
204
+ this.previouslyFocused = document.activeElement;
205
+ document.addEventListener('keydown', this.handleGlobalKeyDown);
206
+ this.lockScroll(true);
207
+ // Wait a frame so the panel exists before moving focus into it.
208
+ requestAnimationFrame(() => this.focusFirst());
209
+ }
210
+ else {
211
+ document.removeEventListener('keydown', this.handleGlobalKeyDown);
212
+ this.lockScroll(false);
213
+ this.previouslyFocused?.focus?.();
214
+ this.previouslyFocused = undefined;
215
+ }
216
+ }
217
+ componentWillLoad() {
218
+ this.syncFooterSlot();
219
+ }
220
+ componentDidLoad() {
221
+ if (this.open)
222
+ this.onOpenChange(true, false);
223
+ }
224
+ disconnectedCallback() {
225
+ document.removeEventListener('keydown', this.handleGlobalKeyDown);
226
+ this.lockScroll(false);
227
+ }
228
+ lockScroll(locked) {
229
+ if (typeof document === 'undefined')
230
+ return;
231
+ document.documentElement.style.overflow = locked ? 'hidden' : '';
232
+ }
233
+ focusableElements() {
234
+ if (!this.panelEl)
235
+ return [];
236
+ const selector = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
237
+ // Slotted content lives in the light DOM, so both trees have to be queried.
238
+ const inShadow = Array.from(this.panelEl.querySelectorAll(selector));
239
+ const inLight = Array.from(this.host.querySelectorAll(selector));
240
+ return [...inShadow, ...inLight].filter(el => el.offsetParent !== null || el === document.activeElement);
241
+ }
242
+ focusFirst() {
243
+ const [first] = this.focusableElements();
244
+ (first ?? this.panelEl)?.focus?.();
245
+ }
246
+ render() {
247
+ if (!this.open)
248
+ return h(Host, { "aria-hidden": "true" });
249
+ return (h(Host, null, h("div", { class: "backdrop", part: "backdrop", onClick: this.handleBackdrop }, h("div", { class: { panel: true, [`size-${this.size}`]: true }, part: "panel", role: "dialog", "aria-modal": "true", "aria-label": this.heading || 'Dialog', tabindex: -1, ref: el => (this.panelEl = el) }, h("header", { class: "header", part: "header" }, h("slot", { name: "header" }, h("div", { class: "titles" }, h("h2", { class: "heading", part: "heading" }, this.heading), this.subheading && h("p", { class: "subheading" }, this.subheading))), this.dismissible && (h("button", { class: "close", part: "close", type: "button", "aria-label": "Close dialog", onClick: () => this.rkModalClose.emit() }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "aria-hidden": "true" }, h("path", { d: "M6 6l12 12M18 6 6 18" }))))), h("div", { class: "body", part: "body" }, h("slot", null)), h("footer", { class: { footer: true, 'is-empty': !this.hasFooter }, part: "footer" }, h("slot", { name: "footer", onSlotchange: this.syncFooterSlot }))))));
250
+ }
251
+ get host() { return getElement(this); }
252
+ static get watchers() { return {
253
+ "open": [{
254
+ "onOpenChange": 0
255
+ }]
256
+ }; }
257
+ };
258
+ RkModal.style = rkModalCss();
259
+
260
+ const rkRatingCss = () => `:host{display:inline-block;font-family:var(--rk-font);--rk-star-size:18px}.stars{display:inline-flex;align-items:center;gap:2px;border-radius:var(--rk-radius-sm, 8px)}.stars:not(.is-readonly){cursor:pointer}.stars:focus-visible{outline:none;box-shadow:var(--rk-focus-ring)}.star-wrap{display:inline-flex;padding:1px;transition:transform var(--rk-transition)}.stars:not(.is-readonly) .star-wrap:hover{transform:scale(1.15)}.star{width:var(--rk-star-size);height:var(--rk-star-size);display:block}.star-bg{fill:none;stroke:var(--rk-border-strong, #d9c6b8);stroke-width:1.6}.star-fg{fill:var(--rk-warning, #a16207);stroke:var(--rk-warning, #a16207);stroke-width:1.2}.value{margin-left:7px;font-size:0.85em;font-weight:600;color:var(--rk-ink-2, #52443c);font-variant-numeric:tabular-nums}.count{margin-left:4px;font-weight:500;color:var(--rk-muted, #857166)}@media (prefers-reduced-motion: reduce){.star-wrap,.stars:not(.is-readonly) .star-wrap:hover{transition:none;transform:none}}`;
261
+
262
+ const RkRating = class {
263
+ constructor(hostRef) {
264
+ registerInstance(this, hostRef);
265
+ this.rkRatingChange = createEvent(this, "rkRatingChange");
266
+ /** Current rating. Supports halves when `readonly`. */
267
+ this.value = 0;
268
+ /** Number of stars. */
269
+ this.max = 5;
270
+ /** When true the stars are display-only and not focusable. */
271
+ this.readonly = false;
272
+ /** Star size in pixels. */
273
+ this.size = 18;
274
+ /** Accessible label for the group. */
275
+ this.label = 'Rating';
276
+ /** Renders the numeric value next to the stars. */
277
+ this.showValue = false;
278
+ this.hovered = 0;
279
+ this.handleKeyDown = (event) => {
280
+ if (this.readonly)
281
+ return;
282
+ const step = event.key === 'ArrowRight' || event.key === 'ArrowUp' ? 1 : event.key === 'ArrowLeft' || event.key === 'ArrowDown' ? -1 : 0;
283
+ if (step === 0)
284
+ return;
285
+ event.preventDefault();
286
+ const next = Math.min(this.max, Math.max(0, Math.round(this.value) + step));
287
+ this.value = next;
288
+ this.rkRatingChange.emit(next);
289
+ };
290
+ }
291
+ select(next) {
292
+ if (this.readonly)
293
+ return;
294
+ // Clicking the active star clears the rating, which is the usual affordance.
295
+ const resolved = next === this.value ? 0 : next;
296
+ this.value = resolved;
297
+ this.rkRatingChange.emit(resolved);
298
+ }
299
+ render() {
300
+ const active = this.hovered || this.value;
301
+ const stars = Array.from({ length: Math.max(1, Math.round(this.max)) }, (_, index) => index + 1);
302
+ return (h(Host, { key: '2147d57f9887cec317ac42156632a11a92d031bd', style: { '--rk-star-size': `${this.size}px` } }, h("div", { key: '8b6ed7883e64adbebb6cb00166ef1dc3e63d8b6f', class: { stars: true, 'is-readonly': this.readonly }, part: "stars", role: this.readonly ? 'img' : 'slider', "aria-label": `${this.label}: ${this.value} out of ${this.max}`, "aria-valuenow": this.readonly ? null : String(this.value), "aria-valuemin": this.readonly ? null : '0', "aria-valuemax": this.readonly ? null : String(this.max), tabindex: this.readonly ? null : 0, onKeyDown: this.handleKeyDown, onMouseLeave: () => (this.hovered = 0) }, stars.map(star => {
303
+ // Fractional fill only matters in read-only display mode.
304
+ const fill = this.readonly ? Math.min(1, Math.max(0, this.value - (star - 1))) : star <= active ? 1 : 0;
305
+ return (h("span", { class: "star-wrap", key: star, onMouseEnter: () => !this.readonly && (this.hovered = star), onClick: () => this.select(star), "aria-hidden": "true" }, h("svg", { class: "star", viewBox: "0 0 24 24" }, h("path", { class: "star-bg", d: "M12 2.6l2.86 5.98 6.54.87-4.8 4.5 1.2 6.45L12 17.3l-5.8 3.1 1.2-6.45-4.8-4.5 6.54-.87z" }), h("clipPath", { id: `clip-${star}` }, h("rect", { x: "0", y: "0", width: 24 * fill, height: "24" })), h("path", { class: "star-fg", "clip-path": `url(#clip-${star})`, d: "M12 2.6l2.86 5.98 6.54.87-4.8 4.5 1.2 6.45L12 17.3l-5.8 3.1 1.2-6.45-4.8-4.5 6.54-.87z" }))));
306
+ }), this.showValue && (h("span", { key: '0f3fd486aca3d9c05df656e723eeb6a30a7469c2', class: "value", part: "value" }, this.value ? this.value.toFixed(1) : '—', typeof this.count === 'number' && h("span", { key: '40a193f04a145644556dad621fcc3ee65fb101b3', class: "count" }, "(", this.count, ")"))))));
307
+ }
308
+ };
309
+ RkRating.style = rkRatingCss();
310
+
311
+ const rkRecipeCardCss = () => `:host{display:block;font-family:var(--rk-font);font-size:var(--rk-font-size, 15px);color:var(--rk-ink, #211a16);height:100%}.card{position:relative;display:flex;flex-direction:column;height:100%;background:var(--rk-surface, #fff);border:1px solid var(--rk-border, #ecdfd4);border-radius:var(--rk-radius-lg, 20px);overflow:hidden;box-shadow:var(--rk-shadow-sm);transition:transform var(--rk-transition), box-shadow var(--rk-transition), border-color var(--rk-transition)}.card:hover{transform:translateY(-3px);box-shadow:var(--rk-shadow);border-color:var(--rk-border-strong, #d9c6b8)}.media{position:relative;display:block;aspect-ratio:4 / 3;background:var(--rk-surface-2, #fdf5ef);overflow:hidden;text-decoration:none}.media img{width:100%;height:100%;object-fit:cover;display:block;transition:transform 420ms cubic-bezier(0.2, 0, 0.2, 1)}.card:hover .media img{transform:scale(1.045)}.media-fallback{position:absolute;inset:0;display:grid;place-items:center;font-size:2.6rem;opacity:0.5}.owned{position:absolute;left:10px;bottom:10px;padding:4px 10px;border-radius:var(--rk-radius-pill, 999px);background:var(--rk-accent, #0f766e);color:#fff;font-size:0.72em;font-weight:700;letter-spacing:0.03em;text-transform:uppercase}.fav{position:absolute;top:10px;right:10px;z-index:2;display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;padding:0;border:0;border-radius:50%;background:rgba(255, 255, 255, 0.9);backdrop-filter:blur(6px);color:var(--rk-ink-2, #52443c);cursor:pointer;box-shadow:var(--rk-shadow-sm);transition:background var(--rk-transition), color var(--rk-transition), transform var(--rk-transition)}.fav svg{width:20px;height:20px;fill:none;transition:fill var(--rk-transition)}.fav:hover{transform:scale(1.08);color:var(--rk-primary, #d94f1b)}.fav:focus-visible{outline:none;box-shadow:var(--rk-focus-ring)}.fav.is-on{color:var(--rk-primary, #d94f1b)}.fav.is-on svg{fill:currentColor}.body{display:flex;flex-direction:column;gap:7px;padding:15px 16px 0;flex:1 1 auto}.title{margin:0;font-family:var(--rk-font-display, serif);font-size:1.12em;font-weight:650;line-height:1.28;letter-spacing:-0.01em}.title a{color:inherit;text-decoration:none;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.title a:hover{color:var(--rk-primary, #d94f1b)}.title a:focus-visible{outline:none;border-radius:4px;box-shadow:var(--rk-focus-ring)}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:6px 14px;margin:0;font-size:0.83em;color:var(--rk-muted, #857166)}.meta-item{position:relative;white-space:nowrap}.meta-item+.meta-item::before{content:'';position:absolute;left:-8px;top:50%;width:3px;height:3px;border-radius:50%;background:currentColor;opacity:0.55;transform:translateY(-50%)}.extra{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:3px}.footer{display:flex;align-items:center;gap:8px;padding:14px 16px;margin-top:12px;border-top:1px solid var(--rk-border, #ecdfd4)}.footer ::slotted(*){flex:1 1 auto}.extra.is-empty,.footer.is-empty{display:none}.card:has(.footer.is-empty) .body{padding-bottom:16px}.card.is-compact{flex-direction:row;align-items:stretch;border-radius:var(--rk-radius, 14px)}.card.is-compact:hover{transform:none}.card.is-compact .media{flex:0 0 96px;aspect-ratio:1 / 1}.card.is-compact .body{padding:12px 14px;gap:4px;justify-content:center}.card.is-compact .title{font-size:1em}.card.is-compact .fav{width:32px;height:32px;top:8px;right:8px}.card.is-compact .fav svg{width:17px;height:17px}.card.is-compact .footer{margin-top:0;border-top:0;border-left:1px solid var(--rk-border, #ecdfd4);padding:12px}.card.is-compact .owned{display:none}@media (prefers-reduced-motion: reduce){.card,.card:hover,.media img,.card:hover .media img,.fav{transition:none;transform:none}}`;
312
+
313
+ const RkRecipeCard = class {
314
+ constructor(hostRef) {
315
+ registerInstance(this, hostRef);
316
+ this.rkCardSelect = createEvent(this, "rkCardSelect");
317
+ this.rkFavoriteToggle = createEvent(this, "rkFavoriteToggle");
318
+ /** Recipe title. */
319
+ this.name = '';
320
+ /** Whether this recipe is currently favorited. */
321
+ this.favorite = false;
322
+ /** `custom` recipes are user-authored and get an "Owned" badge. */
323
+ this.origin = 'api';
324
+ /** Renders a compact horizontal layout, for dense lists and the planner. */
325
+ this.compact = false;
326
+ /** Hides the favorite button entirely (e.g. inside the planner). */
327
+ this.hideFavorite = false;
328
+ this.imageFailed = false;
329
+ this.hasExtra = false;
330
+ this.hasActions = false;
331
+ /**
332
+ * Slot content is optional, and an empty wrapper would still paint padding and
333
+ * a border. Tracking what was actually projected lets the card collapse those
334
+ * regions instead. Re-run on `slotchange` so framework-driven updates count.
335
+ */
336
+ this.syncSlots = () => {
337
+ const elements = Array.from(this.host.children);
338
+ this.hasActions = elements.some(node => node.getAttribute('slot') === 'actions');
339
+ this.hasExtra =
340
+ elements.some(node => !node.hasAttribute('slot')) ||
341
+ Array.from(this.host.childNodes).some(node => node.nodeType === Node.TEXT_NODE && (node.textContent ?? '').trim() !== '');
342
+ };
343
+ this.handleSelect = (event) => {
344
+ // Let the browser handle modified clicks so "open in new tab" keeps working.
345
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0) {
346
+ return;
347
+ }
348
+ event.preventDefault();
349
+ this.rkCardSelect.emit(this.detail);
350
+ };
351
+ this.handleFavorite = (event) => {
352
+ event.preventDefault();
353
+ event.stopPropagation();
354
+ this.rkFavoriteToggle.emit({ ...this.detail, favorite: !this.favorite });
355
+ };
356
+ }
357
+ componentWillLoad() {
358
+ this.syncSlots();
359
+ }
360
+ get detail() {
361
+ return { recipeId: this.recipeId, name: this.name, origin: this.origin };
362
+ }
363
+ render() {
364
+ const showImage = this.image && !this.imageFailed;
365
+ const href = this.href ?? `#${this.recipeId}`;
366
+ return (h("article", { key: 'abc3f8de736b74b1446ced7f2745c5a60b489c24', class: { card: true, 'is-compact': this.compact }, part: "card" }, h("a", { key: 'c401e8d99a43bbc5b4a67f1b79d02a4afd17fefc', class: "media", part: "media", href: href, tabindex: -1, "aria-hidden": "true", onClick: this.handleSelect }, showImage ? (h("img", { src: this.image, alt: "", loading: "lazy", decoding: "async", onError: () => (this.imageFailed = true) })) : (h("span", { class: "media-fallback", "aria-hidden": "true" }, "\uD83C\uDF72")), this.origin === 'custom' && (h("span", { key: 'd73c90426f8727a313d047db677a92184e57d794', class: "owned", part: "owned-badge" }, "Yours"))), !this.hideFavorite && (h("button", { key: '2f8cbaec4403219b0d39529758ef52bf8cb1fb7a', class: { fav: true, 'is-on': this.favorite }, part: "favorite-button", type: "button", onClick: this.handleFavorite, "aria-pressed": String(this.favorite), "aria-label": this.favorite ? `Remove ${this.name} from favorites` : `Add ${this.name} to favorites`, title: this.favorite ? 'Remove from favorites' : 'Add to favorites' }, h("svg", { key: '5766d053203efbf14be2ef9c6f6e1e7d8fa72552', viewBox: "0 0 24 24", stroke: "currentColor", "stroke-width": "1.9", "stroke-linejoin": "round", "aria-hidden": "true" }, h("path", { key: '1eebb81f40253f26b3ea021dd08cd6fd67b6e2ea', d: "M12 20.5 4.6 13.3a4.7 4.7 0 0 1 0-6.7 4.7 4.7 0 0 1 6.7 0l.7.7.7-.7a4.7 4.7 0 0 1 6.7 0 4.7 4.7 0 0 1 0 6.7Z" })))), h("div", { key: '0dd0feccc789b188cecc42d920e44e39556a9134', class: "body", part: "body" }, h("h3", { key: '29739cf1838d2a2827985104da23e62498c00a00', class: "title", part: "title" }, h("a", { key: 'd42fa3f306da565f8a04179a699fc7b6092839d6', href: href, onClick: this.handleSelect }, this.name)), h("p", { key: '3a16c25552fab648854e3923d6f0a389f916b07f', class: "meta", part: "meta" }, this.category && h("span", { key: 'ea8788f2772055734cc6740fc073c48061ff7b1c', class: "meta-item" }, this.category), this.area && h("span", { key: 'b54bba9cd55cec0296a483efa8a865c8b50dab03', class: "meta-item" }, this.area), this.minutes > 0 && h("span", { key: '11c08808645b3dbd942d5374edf92bcd18af85a1', class: "meta-item" }, this.minutes, " min"), this.servings > 0 && h("span", { key: '28064d47688d06897a3b46ff039d66483e280803', class: "meta-item" }, "Serves ", this.servings)), h("div", { key: '3bf5b7ad91730f16697854287d3853b371d27cf4', class: { extra: true, 'is-empty': !this.hasExtra }, part: "extra" }, h("slot", { key: '1dfe7c29f1d350e85c4cd8122f6fe7e07c60f56f', onSlotchange: this.syncSlots }))), h("footer", { key: '56e115b1e6c46fc5de7a8df9ea005011787ecd51', class: { footer: true, 'is-empty': !this.hasActions }, part: "footer" }, h("slot", { key: 'd07e312d8486a7b761856f5137f24ab95a98c582', name: "actions", onSlotchange: this.syncSlots }))));
367
+ }
368
+ get host() { return getElement(this); }
369
+ };
370
+ RkRecipeCard.style = rkRecipeCardCss();
371
+
372
+ const rkSearchBarCss = () => `:host{display:block;font-family:var(--rk-font);font-size:var(--rk-font-size, 15px);color:var(--rk-ink, #211a16)}.shell{display:flex;align-items:center;gap:10px;padding:0 10px 0 14px;min-height:52px;background:var(--rk-surface, #fff);border:1px solid var(--rk-border, #ecdfd4);border-radius:var(--rk-radius-pill, 999px);box-shadow:var(--rk-shadow-sm);transition:border-color var(--rk-transition), box-shadow var(--rk-transition)}.shell.is-focused{border-color:var(--rk-primary, #d94f1b);box-shadow:var(--rk-focus-ring)}.shell.is-disabled{opacity:0.6;cursor:not-allowed}.icon{display:inline-flex;flex:0 0 auto;color:var(--rk-muted, #857166)}.icon svg{width:20px;height:20px}.input{flex:1 1 auto;min-width:0;border:0;outline:none;background:transparent;font:inherit;color:inherit;padding:14px 0;appearance:none}.input::placeholder{color:var(--rk-muted, #857166);opacity:0.9}.input::-webkit-search-cancel-button,.input::-webkit-search-decoration{appearance:none;display:none}.clear{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:30px;height:30px;padding:0;border:0;border-radius:50%;background:var(--rk-surface-2, #fdf5ef);color:var(--rk-ink-2, #52443c);cursor:pointer;transition:background var(--rk-transition), color var(--rk-transition)}.clear:hover{background:var(--rk-primary-soft, #fdeee6);color:var(--rk-primary, #d94f1b)}.clear:focus-visible{outline:none;box-shadow:var(--rk-focus-ring)}.clear svg{width:15px;height:15px}.spinner{flex:0 0 auto;width:18px;height:18px;border-radius:50%;border:2px solid var(--rk-border-strong, #d9c6b8);border-top-color:var(--rk-primary, #d94f1b);animation:rk-spin 700ms linear infinite}@keyframes rk-spin{to{transform:rotate(360deg)}}.actions{display:contents}.actions ::slotted(*){flex:0 0 auto}.hint{margin:8px 0 0;padding-left:16px;font-size:0.82em;color:var(--rk-muted, #857166)}@media (prefers-reduced-motion: reduce){.spinner{animation-duration:2s}.shell,.clear{transition:none}}`;
373
+
374
+ const RkSearchBar = class {
375
+ constructor(hostRef) {
376
+ registerInstance(this, hostRef);
377
+ this.rkSearch = createEvent(this, "rkSearch");
378
+ this.rkClear = createEvent(this, "rkClear");
379
+ /** Current text in the field. Two-way: updated as the user types. */
380
+ this.value = '';
381
+ /** Placeholder shown when the field is empty. */
382
+ this.placeholder = 'Search recipes, ingredients, cuisines…';
383
+ /** Accessible label for the input. */
384
+ this.label = 'Search recipes';
385
+ /** Milliseconds to wait after the last keystroke before emitting `rkSearch`. */
386
+ this.debounce = 350;
387
+ /** Disables the whole control. */
388
+ this.disabled = false;
389
+ /** Shows a busy indicator (e.g. while the app is fetching results). */
390
+ this.loading = false;
391
+ this.focused = false;
392
+ this.handleInput = (event) => {
393
+ const next = event.target.value;
394
+ this.value = next;
395
+ clearTimeout(this.timer);
396
+ this.timer = setTimeout(() => this.emit(next), Math.max(0, this.debounce));
397
+ };
398
+ this.handleKeyDown = (event) => {
399
+ if (event.key === 'Enter') {
400
+ event.preventDefault();
401
+ this.emit(this.value);
402
+ }
403
+ else if (event.key === 'Escape' && this.value) {
404
+ event.preventDefault();
405
+ this.handleClear();
406
+ }
407
+ };
408
+ this.handleClear = () => {
409
+ clearTimeout(this.timer);
410
+ this.value = '';
411
+ if (this.inputEl)
412
+ this.inputEl.value = '';
413
+ this.rkClear.emit();
414
+ this.rkSearch.emit('');
415
+ this.inputEl?.focus();
416
+ };
417
+ }
418
+ onValueChange(next, prev) {
419
+ // Keep the DOM input in sync when the value is driven from the outside.
420
+ if (this.inputEl && this.inputEl.value !== next && next !== prev) {
421
+ this.inputEl.value = next ?? '';
422
+ }
423
+ }
424
+ disconnectedCallback() {
425
+ clearTimeout(this.timer);
426
+ }
427
+ /** Programmatically focus the input. */
428
+ async setFocus() {
429
+ this.inputEl?.focus();
430
+ }
431
+ emit(query) {
432
+ clearTimeout(this.timer);
433
+ this.rkSearch.emit(query.trim());
434
+ }
435
+ render() {
436
+ const hasValue = (this.value ?? '').length > 0;
437
+ return (h(Host, { key: 'cf58faf063a6c13bada8f5713f0dcbb582962577' }, h("div", { key: '13bdb192a7cb4fd699ffae8a9f67fba3e1a68c16', class: { shell: true, 'is-focused': this.focused, 'is-disabled': this.disabled }, part: "shell" }, h("span", { key: 'fe65bb803f4e3c01c1bf1eab66ab70658256820e', class: "icon", "aria-hidden": "true" }, h("svg", { key: 'a20ca316e086189ad00f8e9fed05cfa21c43fa10', viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round" }, h("circle", { key: '8298c51dc269224356a1e3248838e2ff8c20aab3', cx: "11", cy: "11", r: "7" }), h("path", { key: '92acd9c5a363ad4a9e9f14cbcf6b4ff86aae2bf2', d: "m20 20-3.5-3.5" }))), h("input", { key: '5fef80ea1e552630361556381dd09b62874334ca', ref: el => (this.inputEl = el), class: "input", part: "input", type: "search", value: this.value, placeholder: this.placeholder, "aria-label": this.label, "aria-busy": this.loading ? 'true' : 'false', disabled: this.disabled, autocomplete: "off", autocapitalize: "off", spellcheck: false, enterkeyhint: "search", onInput: this.handleInput, onKeyDown: this.handleKeyDown, onFocus: () => (this.focused = true), onBlur: () => (this.focused = false) }), this.loading && h("span", { key: '17c0e5483cc3bea79642192c6cb8548f32071f02', class: "spinner", part: "spinner", "aria-hidden": "true" }), hasValue && !this.loading && (h("button", { key: '7df966f93f1de6a1d3a380c4856f235a671521ad', class: "clear", part: "clear", type: "button", onClick: this.handleClear, "aria-label": "Clear search", disabled: this.disabled }, h("svg", { key: '29457188a630e71e4c29f93939edbcb28862cade', viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "aria-hidden": "true" }, h("path", { key: '64406c87eaaa582151fdf5d1ee7929a6f311cc4c', d: "M6 6l12 12M18 6 6 18" })))), h("span", { key: '7abf5bd0cdb2d15b7143491c5de5216df872deff', class: "actions", part: "actions" }, h("slot", { key: '09fa60e54e72af6c9e7187f3406a11e80d5a3004', name: "actions" }))), this.hint && (h("p", { key: '1dd826505bcfc2cb5b7712adc18e7e29dc814f2b', class: "hint", part: "hint" }, this.hint))));
438
+ }
439
+ static get watchers() { return {
440
+ "value": [{
441
+ "onValueChange": 0
442
+ }]
443
+ }; }
444
+ };
445
+ RkSearchBar.style = rkSearchBarCss();
446
+
447
+ const rkSegmentedControlCss = () => `:host{display:inline-block;font-family:var(--rk-font);font-size:var(--rk-font-size, 15px);max-width:100%}.group{display:inline-flex;align-items:center;gap:2px;padding:3px;background:var(--rk-surface-2, #fdf5ef);border:1px solid var(--rk-border, #ecdfd4);border-radius:var(--rk-radius-pill, 999px);max-width:100%;overflow-x:auto;scrollbar-width:none}.group::-webkit-scrollbar{display:none}.group.is-stretch{display:flex;width:100%}.segment{display:inline-flex;align-items:center;gap:6px;flex:0 0 auto;padding:8px 16px;border:0;border-radius:var(--rk-radius-pill, 999px);background:transparent;color:var(--rk-ink-2, #52443c);font:inherit;font-size:0.88em;font-weight:550;line-height:1;white-space:nowrap;cursor:pointer;transition:background var(--rk-transition), color var(--rk-transition), box-shadow var(--rk-transition)}.group.is-stretch .segment{flex:1 1 0;justify-content:center}.segment:hover:not(:disabled):not(.is-selected){background:rgba(0, 0, 0, 0.04);color:var(--rk-ink, #211a16)}.segment:focus-visible{outline:none;box-shadow:var(--rk-focus-ring)}.segment.is-selected{background:var(--rk-surface, #fff);color:var(--rk-ink, #211a16);box-shadow:var(--rk-shadow-sm);font-weight:650}.segment:disabled{opacity:0.45;cursor:not-allowed}.count{font-size:0.85em;font-weight:650;color:var(--rk-muted, #857166);font-variant-numeric:tabular-nums}.segment.is-selected .count{color:var(--rk-primary, #d94f1b)}@media (prefers-reduced-motion: reduce){.segment{transition:none}}`;
448
+
449
+ const RkSegmentedControl = class {
450
+ constructor(hostRef) {
451
+ registerInstance(this, hostRef);
452
+ this.rkSegmentChange = createEvent(this, "rkSegmentChange");
453
+ /** The available segments. Accepts an array or a JSON-encoded string. */
454
+ this.options = [];
455
+ /** Accessible label for the group. */
456
+ this.label = 'View options';
457
+ /** Stretches the control to fill its container. */
458
+ this.stretch = false;
459
+ this.parsed = [];
460
+ this.handleKeyDown = (event, index) => {
461
+ const step = event.key === 'ArrowRight' ? 1 : event.key === 'ArrowLeft' ? -1 : 0;
462
+ if (step === 0)
463
+ return;
464
+ event.preventDefault();
465
+ const items = this.parsed.filter(option => !option.disabled);
466
+ if (items.length === 0)
467
+ return;
468
+ const currentIndex = items.indexOf(this.parsed[index]);
469
+ const next = items[(currentIndex + step + items.length) % items.length];
470
+ this.select(next);
471
+ };
472
+ }
473
+ onOptionsChange() {
474
+ this.parseOptions();
475
+ }
476
+ componentWillLoad() {
477
+ this.parseOptions();
478
+ }
479
+ /**
480
+ * Attributes can only carry strings, so a JSON payload is accepted as a
481
+ * fallback for consumers that cannot set DOM properties.
482
+ */
483
+ parseOptions() {
484
+ if (Array.isArray(this.options)) {
485
+ this.parsed = this.options;
486
+ return;
487
+ }
488
+ if (typeof this.options === 'string' && this.options.trim()) {
489
+ try {
490
+ const decoded = JSON.parse(this.options);
491
+ this.parsed = Array.isArray(decoded) ? decoded : [];
492
+ }
493
+ catch {
494
+ console.warn('[rk-segmented-control] `options` is not valid JSON:', this.options);
495
+ this.parsed = [];
496
+ }
497
+ return;
498
+ }
499
+ this.parsed = [];
500
+ }
501
+ select(option) {
502
+ if (option.disabled || option.value === this.value)
503
+ return;
504
+ this.value = option.value;
505
+ this.rkSegmentChange.emit(option.value);
506
+ }
507
+ render() {
508
+ return (h("div", { key: 'e2b6eec632ebe51b9f259c60b174126de1a9521c', class: { group: true, 'is-stretch': this.stretch }, part: "group", role: "tablist", "aria-label": this.label }, this.parsed.map((option, index) => {
509
+ const selected = option.value === this.value;
510
+ return (h("button", { key: option.value, class: { segment: true, 'is-selected': selected }, part: selected ? 'segment segment-selected' : 'segment', type: "button", role: "tab", "aria-selected": String(selected), disabled: option.disabled, tabindex: selected ? 0 : -1, onClick: () => this.select(option), onKeyDown: event => this.handleKeyDown(event, index) }, option.label, typeof option.count === 'number' && h("span", { class: "count" }, option.count)));
511
+ })));
512
+ }
513
+ static get watchers() { return {
514
+ "options": [{
515
+ "onOptionsChange": 0
516
+ }]
517
+ }; }
518
+ };
519
+ RkSegmentedControl.style = rkSegmentedControlCss();
520
+
521
+ const rkTagInputCss = () => `:host{display:block;font-family:var(--rk-font);font-size:var(--rk-font-size, 15px);color:var(--rk-ink, #211a16)}.label{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:7px;font-size:0.88em;font-weight:600;color:var(--rk-ink-2, #52443c);cursor:text}.counter{font-size:0.88em;font-weight:500;color:var(--rk-muted, #857166);font-variant-numeric:tabular-nums}.shell{display:flex;flex-wrap:wrap;align-items:center;gap:6px;padding:8px 10px;min-height:46px;background:var(--rk-surface, #fff);border:1px solid var(--rk-border, #ecdfd4);border-radius:var(--rk-radius, 14px);cursor:text;transition:border-color var(--rk-transition), box-shadow var(--rk-transition)}.shell:focus-within{border-color:var(--rk-primary, #d94f1b);box-shadow:var(--rk-focus-ring)}.shell.has-error{border-color:var(--rk-danger, #c2410c)}.shell.has-error:focus-within{box-shadow:0 0 0 3px rgba(194, 65, 12, 0.24)}.shell.is-disabled{opacity:0.6;cursor:not-allowed;background:var(--rk-surface-2, #fdf5ef)}.tag{display:inline-flex;align-items:center;gap:2px;padding:4px 4px 4px 11px;border-radius:var(--rk-radius-pill, 999px);background:var(--rk-primary-soft, #fdeee6);color:var(--rk-primary-strong, #b23c11);font-size:0.85em;font-weight:600;max-width:100%}.tag-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tag-remove{display:inline-flex;align-items:center;justify-content:center;width:19px;height:19px;padding:0;border:0;border-radius:50%;background:transparent;color:inherit;cursor:pointer;opacity:0.7;transition:background var(--rk-transition), opacity var(--rk-transition)}.tag-remove:hover:not(:disabled){opacity:1;background:rgba(178, 60, 17, 0.16)}.tag-remove:focus-visible{outline:none;opacity:1;box-shadow:var(--rk-focus-ring)}.tag-remove:disabled{cursor:not-allowed}.tag-remove svg{width:10px;height:10px}.entry{flex:1 1 120px;min-width:110px;border:0;outline:none;background:transparent;font:inherit;color:inherit;padding:5px 2px}.entry::placeholder{color:var(--rk-muted, #857166)}.entry:disabled{cursor:not-allowed}.error{margin:7px 0 0;font-size:0.84em;color:var(--rk-danger, #c2410c);font-weight:550}@media (prefers-reduced-motion: reduce){.shell,.tag-remove{transition:none}}`;
522
+
523
+ const RkTagInput = class {
524
+ constructor(hostRef) {
525
+ registerInstance(this, hostRef);
526
+ this.rkTagsChange = createEvent(this, "rkTagsChange");
527
+ /** Current tags. Array, JSON string, or comma-separated string. */
528
+ this.values = [];
529
+ /** Placeholder for the entry field. */
530
+ this.placeholder = 'Add a tag and press Enter';
531
+ /** Maximum number of tags. `0` means unlimited. */
532
+ this.maxTags = 0;
533
+ /** Prevent editing. */
534
+ this.disabled = false;
535
+ this.draft = '';
536
+ this.tags = [];
537
+ this.handleKeyDown = (event) => {
538
+ if (event.key === 'Enter' || event.key === ',') {
539
+ event.preventDefault();
540
+ this.addDraft();
541
+ }
542
+ else if (event.key === 'Backspace' && this.draft === '' && this.tags.length > 0) {
543
+ event.preventDefault();
544
+ this.removeAt(this.tags.length - 1);
545
+ }
546
+ };
547
+ }
548
+ onValuesChange() {
549
+ this.syncTags();
550
+ }
551
+ componentWillLoad() {
552
+ this.syncTags();
553
+ }
554
+ syncTags() {
555
+ const incoming = this.values;
556
+ if (Array.isArray(incoming)) {
557
+ this.tags = incoming.filter(tag => typeof tag === 'string');
558
+ return;
559
+ }
560
+ if (typeof incoming === 'string' && incoming.trim()) {
561
+ const trimmed = incoming.trim();
562
+ if (trimmed.startsWith('[')) {
563
+ try {
564
+ const decoded = JSON.parse(trimmed);
565
+ this.tags = Array.isArray(decoded) ? decoded.map(String) : [];
566
+ return;
567
+ }
568
+ catch {
569
+ /* fall through to comma parsing */
570
+ }
571
+ }
572
+ this.tags = trimmed
573
+ .split(',')
574
+ .map(tag => tag.trim())
575
+ .filter(Boolean);
576
+ return;
577
+ }
578
+ this.tags = [];
579
+ }
580
+ get atCapacity() {
581
+ return this.maxTags > 0 && this.tags.length >= this.maxTags;
582
+ }
583
+ commit(next) {
584
+ this.tags = next;
585
+ this.rkTagsChange.emit([...next]);
586
+ }
587
+ addDraft() {
588
+ const candidates = this.draft
589
+ .split(',')
590
+ .map(tag => tag.trim())
591
+ .filter(Boolean);
592
+ if (candidates.length === 0)
593
+ return;
594
+ const next = [...this.tags];
595
+ for (const candidate of candidates) {
596
+ if (this.maxTags > 0 && next.length >= this.maxTags)
597
+ break;
598
+ // Case-insensitive de-duplication keeps "Vegan" and "vegan" from coexisting.
599
+ if (next.some(tag => tag.toLowerCase() === candidate.toLowerCase()))
600
+ continue;
601
+ next.push(candidate);
602
+ }
603
+ this.draft = '';
604
+ if (next.length !== this.tags.length)
605
+ this.commit(next);
606
+ }
607
+ removeAt(index) {
608
+ this.commit(this.tags.filter((_, position) => position !== index));
609
+ }
610
+ render() {
611
+ return (h(Host, { key: '446657dc603ffa9e4d14ee5c54c947aef90bab4f' }, this.label && (h("label", { key: '37575fae490ba8e1a03a3aa6e25ba46d2b364d9e', class: "label", part: "label", onClick: () => this.inputEl?.focus() }, this.label, this.maxTags > 0 && (h("span", { key: 'e29c5d1fd9038cee4707c310adfb88ce7cc75308', class: "counter" }, this.tags.length, "/", this.maxTags)))), h("div", { key: '13d171bce0dd090bd3034e0150e2def80f493960', class: { shell: true, 'is-disabled': this.disabled, 'has-error': !!this.error }, part: "shell", onClick: () => !this.disabled && this.inputEl?.focus() }, this.tags.map((tag, index) => (h("span", { class: "tag", part: "tag", key: `${tag}-${index}` }, h("span", { class: "tag-text" }, tag), h("button", { class: "tag-remove", type: "button", "aria-label": `Remove ${tag}`, disabled: this.disabled, onClick: event => {
612
+ event.stopPropagation();
613
+ this.removeAt(index);
614
+ } }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2.6", "stroke-linecap": "round", "aria-hidden": "true" }, h("path", { d: "M6 6l12 12M18 6 6 18" })))))), h("input", { key: '8ccdc5e12bd89f7c6b7b8d3c0f5bf80ec7473224', ref: el => (this.inputEl = el), class: "entry", part: "input", type: "text", value: this.draft, placeholder: this.tags.length === 0 ? this.placeholder : this.atCapacity ? '' : 'Add another…', "aria-label": this.label ?? 'Add a tag', disabled: this.disabled || this.atCapacity, autocomplete: "off", onInput: event => (this.draft = event.target.value), onKeyDown: this.handleKeyDown, onBlur: () => this.addDraft() })), this.error && (h("p", { key: '31e84622ac580903e7ebb6af7f57b1842572ce9f', class: "error", part: "error", role: "alert" }, this.error))));
615
+ }
616
+ static get watchers() { return {
617
+ "values": [{
618
+ "onValuesChange": 0
619
+ }]
620
+ }; }
621
+ };
622
+ RkTagInput.style = rkTagInputCss();
623
+
624
+ export { RkBadge as rk_badge, RkChip as rk_chip, RkEmptyState as rk_empty_state, RkMealSlot as rk_meal_slot, RkModal as rk_modal, RkRating as rk_rating, RkRecipeCard as rk_recipe_card, RkSearchBar as rk_search_bar, RkSegmentedControl as rk_segmented_control, RkTagInput as rk_tag_input };
625
+ //# sourceMappingURL=rk-badge.rk-chip.rk-empty-state.rk-meal-slot.rk-modal.rk-rating.rk-recipe-card.rk-search-bar.rk-segmented-control.rk-tag-input.entry.js.map
626
+
627
+ //# sourceMappingURL=rk-badge_10.entry.js.map