@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,638 @@
1
+ 'use strict';
2
+
3
+ var index = require('./index-BEr8BsSA.js');
4
+
5
+ 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)}`;
6
+
7
+ const RkBadge = class {
8
+ constructor(hostRef) {
9
+ index.registerInstance(this, hostRef);
10
+ /** Color treatment. */
11
+ this.tone = 'neutral';
12
+ /** `soft` is tinted, `solid` is filled, `outline` is bordered only. */
13
+ this.variant = 'soft';
14
+ /** Compact sizing for dense layouts. */
15
+ this.small = false;
16
+ }
17
+ render() {
18
+ return (index.h("span", { key: '874569517e9ef5f7eeeac3435437d7c9671f3017', class: { badge: true, [`tone-${this.tone}`]: true, [`variant-${this.variant}`]: true, 'is-small': this.small }, part: "badge" }, index.h("slot", { key: '8dbd64b3916c73bc377c3e4153daaf88f339e5d1' })));
19
+ }
20
+ };
21
+ RkBadge.style = rkBadgeCss();
22
+
23
+ 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}}`;
24
+
25
+ const RkChip = class {
26
+ constructor(hostRef) {
27
+ index.registerInstance(this, hostRef);
28
+ this.rkChipToggle = index.createEvent(this, "rkChipToggle");
29
+ this.rkChipDismiss = index.createEvent(this, "rkChipDismiss");
30
+ /** Visible text. */
31
+ this.label = '';
32
+ /** Whether the chip is currently active. */
33
+ this.selected = false;
34
+ /** Renders an "x" button that emits `rkChipDismiss` instead of toggling. */
35
+ this.dismissible = false;
36
+ /** Disables interaction. */
37
+ this.disabled = false;
38
+ /** Visual treatment. */
39
+ this.tone = 'neutral';
40
+ this.handleClick = () => {
41
+ if (this.disabled)
42
+ return;
43
+ this.rkChipToggle.emit({
44
+ value: this.resolvedValue,
45
+ label: this.label,
46
+ selected: !this.selected,
47
+ });
48
+ };
49
+ this.handleDismiss = (event) => {
50
+ event.stopPropagation();
51
+ if (this.disabled)
52
+ return;
53
+ this.rkChipDismiss.emit({ value: this.resolvedValue, label: this.label });
54
+ };
55
+ }
56
+ get resolvedValue() {
57
+ return this.value ?? this.label;
58
+ }
59
+ render() {
60
+ const body = [
61
+ index.h("span", { class: "lead" }, index.h("slot", null)),
62
+ index.h("span", { class: "label", part: "label" }, this.label),
63
+ typeof this.count === 'number' && (index.h("span", { class: "count", part: "count" }, this.count)),
64
+ ];
65
+ const classes = {
66
+ chip: true,
67
+ 'is-selected': this.selected,
68
+ 'is-dismissible': this.dismissible,
69
+ [`tone-${this.tone}`]: true,
70
+ };
71
+ // A dismissible chip needs two independent controls, so the shell becomes a
72
+ // plain element to avoid nesting buttons inside a button.
73
+ if (this.dismissible) {
74
+ return (index.h("span", { class: classes, part: "chip" }, index.h("button", { class: "body", type: "button", disabled: this.disabled, onClick: this.handleClick }, body), index.h("button", { class: "dismiss", part: "dismiss", type: "button", disabled: this.disabled, "aria-label": `Remove ${this.label}`, onClick: this.handleDismiss }, index.h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2.4", "stroke-linecap": "round", "aria-hidden": "true" }, index.h("path", { d: "M6 6l12 12M18 6 6 18" })))));
75
+ }
76
+ return (index.h("button", { class: classes, part: "chip", type: "button", "aria-pressed": String(this.selected), disabled: this.disabled, onClick: this.handleClick }, body));
77
+ }
78
+ };
79
+ RkChip.style = rkChipCss();
80
+
81
+ 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}`;
82
+
83
+ const RkEmptyState = class {
84
+ constructor(hostRef) {
85
+ index.registerInstance(this, hostRef);
86
+ /** Emoji or short glyph shown above the heading. */
87
+ this.icon = '🍳';
88
+ /** Bold headline. */
89
+ this.heading = 'Nothing here yet';
90
+ /** `error` swaps to a warning treatment. */
91
+ this.tone = 'neutral';
92
+ /** Removes the dashed border and reduces padding, for inline use. */
93
+ this.bare = false;
94
+ }
95
+ render() {
96
+ return (index.h("div", { key: 'd770d00625d0f8933704195e975ff96e2599a8a9', class: { empty: true, [`tone-${this.tone}`]: true, 'is-bare': this.bare }, part: "container", role: this.tone === 'error' ? 'alert' : null }, index.h("span", { key: '566dc86da2eaaa9765c3112f381aa409bdcf5198', class: "icon", part: "icon", "aria-hidden": "true" }, this.icon), index.h("h3", { key: '9d8e62cc4724b970b6c16dd51797e6c480bf38d2', class: "heading", part: "heading" }, this.heading), this.message && (index.h("p", { key: 'b200ce21253e8cd7f9aa73b513030656db956a5e', class: "message", part: "message" }, this.message)), index.h("div", { key: '1d04761bf9fa953362826e13be1fc95eebc9067a', class: "actions", part: "actions" }, index.h("slot", { key: 'f1302bd354850c09e6fb4b136a32759040b30ecd' }))));
97
+ }
98
+ };
99
+ RkEmptyState.style = rkEmptyStateCss();
100
+
101
+ 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}}`;
102
+
103
+ const RkMealSlot = class {
104
+ constructor(hostRef) {
105
+ index.registerInstance(this, hostRef);
106
+ this.rkMealAdd = index.createEvent(this, "rkMealAdd");
107
+ this.rkMealSwap = index.createEvent(this, "rkMealSwap");
108
+ this.rkMealRemove = index.createEvent(this, "rkMealRemove");
109
+ this.rkMealOpen = index.createEvent(this, "rkMealOpen");
110
+ this.rkMealDrop = index.createEvent(this, "rkMealDrop");
111
+ /** Disables interaction. */
112
+ this.disabled = false;
113
+ this.dragOver = false;
114
+ this.handleDragOver = (event) => {
115
+ if (this.disabled)
116
+ return;
117
+ // Preventing default is what marks this element as a valid drop target.
118
+ event.preventDefault();
119
+ if (event.dataTransfer)
120
+ event.dataTransfer.dropEffect = 'copy';
121
+ this.dragOver = true;
122
+ };
123
+ this.handleDrop = (event) => {
124
+ event.preventDefault();
125
+ this.dragOver = false;
126
+ if (this.disabled)
127
+ return;
128
+ const payload = event.dataTransfer?.getData('text/plain') ?? '';
129
+ if (payload)
130
+ this.rkMealDrop.emit({ ...this.slotDetail, payload });
131
+ };
132
+ }
133
+ get filled() {
134
+ return !!this.recipeId;
135
+ }
136
+ get slotDetail() {
137
+ return { day: this.day, mealType: this.mealType };
138
+ }
139
+ get recipeDetail() {
140
+ return { ...this.slotDetail, recipeId: this.recipeId ?? '', recipeName: this.recipeName ?? '' };
141
+ }
142
+ render() {
143
+ if (!this.filled) {
144
+ return (index.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 }, index.h("span", { class: "plus", "aria-hidden": "true" }, "+"), index.h("span", { class: "empty-label" }, this.mealLabel ?? this.mealType)));
145
+ }
146
+ return (index.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 && (index.h("span", { class: "caption", part: "caption" }, this.mealLabel)), index.h("button", { class: "main", part: "main", type: "button", disabled: this.disabled, title: this.recipeName, onClick: () => this.rkMealOpen.emit(this.recipeDetail) }, this.recipeImage ? (index.h("img", { class: "thumb", src: this.recipeImage, alt: "", loading: "lazy", decoding: "async" })) : (index.h("span", { class: "thumb thumb-fallback", "aria-hidden": "true" }, "\uD83C\uDF7D\uFE0F")), index.h("span", { class: "name", part: "name" }, this.recipeName)), index.h("div", { class: "extra" }, index.h("slot", null)), index.h("div", { class: "controls", part: "controls" }, index.h("button", { class: "ctrl", type: "button", disabled: this.disabled, "aria-label": `Replace ${this.recipeName}`, title: "Replace", onClick: () => this.rkMealSwap.emit(this.recipeDetail) }, index.h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true" }, index.h("path", { d: "M3 12a9 9 0 0 1 15.3-6.4L21 8" }), index.h("path", { d: "M21 3v5h-5" }), index.h("path", { d: "M21 12a9 9 0 0 1-15.3 6.4L3 16" }), index.h("path", { d: "M3 21v-5h5" }))), index.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) }, index.h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "aria-hidden": "true" }, index.h("path", { d: "M6 6l12 12M18 6 6 18" }))))));
147
+ }
148
+ };
149
+ RkMealSlot.style = rkMealSlotCss();
150
+
151
+ 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}}`;
152
+
153
+ const RkModal = class {
154
+ constructor(hostRef) {
155
+ index.registerInstance(this, hostRef);
156
+ this.rkModalClose = index.createEvent(this, "rkModalClose");
157
+ /** Controls visibility. */
158
+ this.open = false;
159
+ /** Heading text for the default header. */
160
+ this.heading = '';
161
+ /** Dialog width preset. */
162
+ this.size = 'md';
163
+ /** Set to false to require an explicit action to dismiss. */
164
+ this.dismissible = true;
165
+ this.hasFooter = false;
166
+ /** Collapses the footer region when no action row was projected into it. */
167
+ this.syncFooterSlot = () => {
168
+ this.hasFooter = Array.from(this.host.children).some(node => node.getAttribute('slot') === 'footer');
169
+ };
170
+ this.handleGlobalKeyDown = (event) => {
171
+ if (!this.open)
172
+ return;
173
+ if (event.key === 'Escape' && this.dismissible) {
174
+ event.preventDefault();
175
+ this.rkModalClose.emit();
176
+ return;
177
+ }
178
+ if (event.key === 'Tab') {
179
+ const focusable = this.focusableElements();
180
+ if (focusable.length === 0)
181
+ return;
182
+ const first = focusable[0];
183
+ const last = focusable[focusable.length - 1];
184
+ const active = (this.host.shadowRoot?.activeElement ?? document.activeElement);
185
+ if (event.shiftKey && active === first) {
186
+ event.preventDefault();
187
+ last.focus();
188
+ }
189
+ else if (!event.shiftKey && active === last) {
190
+ event.preventDefault();
191
+ first.focus();
192
+ }
193
+ }
194
+ };
195
+ this.handleBackdrop = (event) => {
196
+ if (!this.dismissible)
197
+ return;
198
+ if (event.target === event.currentTarget)
199
+ this.rkModalClose.emit();
200
+ };
201
+ }
202
+ onOpenChange(isOpen, wasOpen) {
203
+ if (isOpen === wasOpen)
204
+ return;
205
+ if (isOpen) {
206
+ this.previouslyFocused = document.activeElement;
207
+ document.addEventListener('keydown', this.handleGlobalKeyDown);
208
+ this.lockScroll(true);
209
+ // Wait a frame so the panel exists before moving focus into it.
210
+ requestAnimationFrame(() => this.focusFirst());
211
+ }
212
+ else {
213
+ document.removeEventListener('keydown', this.handleGlobalKeyDown);
214
+ this.lockScroll(false);
215
+ this.previouslyFocused?.focus?.();
216
+ this.previouslyFocused = undefined;
217
+ }
218
+ }
219
+ componentWillLoad() {
220
+ this.syncFooterSlot();
221
+ }
222
+ componentDidLoad() {
223
+ if (this.open)
224
+ this.onOpenChange(true, false);
225
+ }
226
+ disconnectedCallback() {
227
+ document.removeEventListener('keydown', this.handleGlobalKeyDown);
228
+ this.lockScroll(false);
229
+ }
230
+ lockScroll(locked) {
231
+ if (typeof document === 'undefined')
232
+ return;
233
+ document.documentElement.style.overflow = locked ? 'hidden' : '';
234
+ }
235
+ focusableElements() {
236
+ if (!this.panelEl)
237
+ return [];
238
+ const selector = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
239
+ // Slotted content lives in the light DOM, so both trees have to be queried.
240
+ const inShadow = Array.from(this.panelEl.querySelectorAll(selector));
241
+ const inLight = Array.from(this.host.querySelectorAll(selector));
242
+ return [...inShadow, ...inLight].filter(el => el.offsetParent !== null || el === document.activeElement);
243
+ }
244
+ focusFirst() {
245
+ const [first] = this.focusableElements();
246
+ (first ?? this.panelEl)?.focus?.();
247
+ }
248
+ render() {
249
+ if (!this.open)
250
+ return index.h(index.Host, { "aria-hidden": "true" });
251
+ return (index.h(index.Host, null, index.h("div", { class: "backdrop", part: "backdrop", onClick: this.handleBackdrop }, index.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) }, index.h("header", { class: "header", part: "header" }, index.h("slot", { name: "header" }, index.h("div", { class: "titles" }, index.h("h2", { class: "heading", part: "heading" }, this.heading), this.subheading && index.h("p", { class: "subheading" }, this.subheading))), this.dismissible && (index.h("button", { class: "close", part: "close", type: "button", "aria-label": "Close dialog", onClick: () => this.rkModalClose.emit() }, index.h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "aria-hidden": "true" }, index.h("path", { d: "M6 6l12 12M18 6 6 18" }))))), index.h("div", { class: "body", part: "body" }, index.h("slot", null)), index.h("footer", { class: { footer: true, 'is-empty': !this.hasFooter }, part: "footer" }, index.h("slot", { name: "footer", onSlotchange: this.syncFooterSlot }))))));
252
+ }
253
+ get host() { return index.getElement(this); }
254
+ static get watchers() { return {
255
+ "open": [{
256
+ "onOpenChange": 0
257
+ }]
258
+ }; }
259
+ };
260
+ RkModal.style = rkModalCss();
261
+
262
+ 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}}`;
263
+
264
+ const RkRating = class {
265
+ constructor(hostRef) {
266
+ index.registerInstance(this, hostRef);
267
+ this.rkRatingChange = index.createEvent(this, "rkRatingChange");
268
+ /** Current rating. Supports halves when `readonly`. */
269
+ this.value = 0;
270
+ /** Number of stars. */
271
+ this.max = 5;
272
+ /** When true the stars are display-only and not focusable. */
273
+ this.readonly = false;
274
+ /** Star size in pixels. */
275
+ this.size = 18;
276
+ /** Accessible label for the group. */
277
+ this.label = 'Rating';
278
+ /** Renders the numeric value next to the stars. */
279
+ this.showValue = false;
280
+ this.hovered = 0;
281
+ this.handleKeyDown = (event) => {
282
+ if (this.readonly)
283
+ return;
284
+ const step = event.key === 'ArrowRight' || event.key === 'ArrowUp' ? 1 : event.key === 'ArrowLeft' || event.key === 'ArrowDown' ? -1 : 0;
285
+ if (step === 0)
286
+ return;
287
+ event.preventDefault();
288
+ const next = Math.min(this.max, Math.max(0, Math.round(this.value) + step));
289
+ this.value = next;
290
+ this.rkRatingChange.emit(next);
291
+ };
292
+ }
293
+ select(next) {
294
+ if (this.readonly)
295
+ return;
296
+ // Clicking the active star clears the rating, which is the usual affordance.
297
+ const resolved = next === this.value ? 0 : next;
298
+ this.value = resolved;
299
+ this.rkRatingChange.emit(resolved);
300
+ }
301
+ render() {
302
+ const active = this.hovered || this.value;
303
+ const stars = Array.from({ length: Math.max(1, Math.round(this.max)) }, (_, index) => index + 1);
304
+ return (index.h(index.Host, { key: '2147d57f9887cec317ac42156632a11a92d031bd', style: { '--rk-star-size': `${this.size}px` } }, index.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 => {
305
+ // Fractional fill only matters in read-only display mode.
306
+ const fill = this.readonly ? Math.min(1, Math.max(0, this.value - (star - 1))) : star <= active ? 1 : 0;
307
+ return (index.h("span", { class: "star-wrap", key: star, onMouseEnter: () => !this.readonly && (this.hovered = star), onClick: () => this.select(star), "aria-hidden": "true" }, index.h("svg", { class: "star", viewBox: "0 0 24 24" }, index.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" }), index.h("clipPath", { id: `clip-${star}` }, index.h("rect", { x: "0", y: "0", width: 24 * fill, height: "24" })), index.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" }))));
308
+ }), this.showValue && (index.h("span", { key: '0f3fd486aca3d9c05df656e723eeb6a30a7469c2', class: "value", part: "value" }, this.value ? this.value.toFixed(1) : '—', typeof this.count === 'number' && index.h("span", { key: '40a193f04a145644556dad621fcc3ee65fb101b3', class: "count" }, "(", this.count, ")"))))));
309
+ }
310
+ };
311
+ RkRating.style = rkRatingCss();
312
+
313
+ 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}}`;
314
+
315
+ const RkRecipeCard = class {
316
+ constructor(hostRef) {
317
+ index.registerInstance(this, hostRef);
318
+ this.rkCardSelect = index.createEvent(this, "rkCardSelect");
319
+ this.rkFavoriteToggle = index.createEvent(this, "rkFavoriteToggle");
320
+ /** Recipe title. */
321
+ this.name = '';
322
+ /** Whether this recipe is currently favorited. */
323
+ this.favorite = false;
324
+ /** `custom` recipes are user-authored and get an "Owned" badge. */
325
+ this.origin = 'api';
326
+ /** Renders a compact horizontal layout, for dense lists and the planner. */
327
+ this.compact = false;
328
+ /** Hides the favorite button entirely (e.g. inside the planner). */
329
+ this.hideFavorite = false;
330
+ this.imageFailed = false;
331
+ this.hasExtra = false;
332
+ this.hasActions = false;
333
+ /**
334
+ * Slot content is optional, and an empty wrapper would still paint padding and
335
+ * a border. Tracking what was actually projected lets the card collapse those
336
+ * regions instead. Re-run on `slotchange` so framework-driven updates count.
337
+ */
338
+ this.syncSlots = () => {
339
+ const elements = Array.from(this.host.children);
340
+ this.hasActions = elements.some(node => node.getAttribute('slot') === 'actions');
341
+ this.hasExtra =
342
+ elements.some(node => !node.hasAttribute('slot')) ||
343
+ Array.from(this.host.childNodes).some(node => node.nodeType === Node.TEXT_NODE && (node.textContent ?? '').trim() !== '');
344
+ };
345
+ this.handleSelect = (event) => {
346
+ // Let the browser handle modified clicks so "open in new tab" keeps working.
347
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0) {
348
+ return;
349
+ }
350
+ event.preventDefault();
351
+ this.rkCardSelect.emit(this.detail);
352
+ };
353
+ this.handleFavorite = (event) => {
354
+ event.preventDefault();
355
+ event.stopPropagation();
356
+ this.rkFavoriteToggle.emit({ ...this.detail, favorite: !this.favorite });
357
+ };
358
+ }
359
+ componentWillLoad() {
360
+ this.syncSlots();
361
+ }
362
+ get detail() {
363
+ return { recipeId: this.recipeId, name: this.name, origin: this.origin };
364
+ }
365
+ render() {
366
+ const showImage = this.image && !this.imageFailed;
367
+ const href = this.href ?? `#${this.recipeId}`;
368
+ return (index.h("article", { key: 'abc3f8de736b74b1446ced7f2745c5a60b489c24', class: { card: true, 'is-compact': this.compact }, part: "card" }, index.h("a", { key: 'c401e8d99a43bbc5b4a67f1b79d02a4afd17fefc', class: "media", part: "media", href: href, tabindex: -1, "aria-hidden": "true", onClick: this.handleSelect }, showImage ? (index.h("img", { src: this.image, alt: "", loading: "lazy", decoding: "async", onError: () => (this.imageFailed = true) })) : (index.h("span", { class: "media-fallback", "aria-hidden": "true" }, "\uD83C\uDF72")), this.origin === 'custom' && (index.h("span", { key: 'd73c90426f8727a313d047db677a92184e57d794', class: "owned", part: "owned-badge" }, "Yours"))), !this.hideFavorite && (index.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' }, index.h("svg", { key: '5766d053203efbf14be2ef9c6f6e1e7d8fa72552', viewBox: "0 0 24 24", stroke: "currentColor", "stroke-width": "1.9", "stroke-linejoin": "round", "aria-hidden": "true" }, index.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" })))), index.h("div", { key: '0dd0feccc789b188cecc42d920e44e39556a9134', class: "body", part: "body" }, index.h("h3", { key: '29739cf1838d2a2827985104da23e62498c00a00', class: "title", part: "title" }, index.h("a", { key: 'd42fa3f306da565f8a04179a699fc7b6092839d6', href: href, onClick: this.handleSelect }, this.name)), index.h("p", { key: '3a16c25552fab648854e3923d6f0a389f916b07f', class: "meta", part: "meta" }, this.category && index.h("span", { key: 'ea8788f2772055734cc6740fc073c48061ff7b1c', class: "meta-item" }, this.category), this.area && index.h("span", { key: 'b54bba9cd55cec0296a483efa8a865c8b50dab03', class: "meta-item" }, this.area), this.minutes > 0 && index.h("span", { key: '11c08808645b3dbd942d5374edf92bcd18af85a1', class: "meta-item" }, this.minutes, " min"), this.servings > 0 && index.h("span", { key: '28064d47688d06897a3b46ff039d66483e280803', class: "meta-item" }, "Serves ", this.servings)), index.h("div", { key: '3bf5b7ad91730f16697854287d3853b371d27cf4', class: { extra: true, 'is-empty': !this.hasExtra }, part: "extra" }, index.h("slot", { key: '1dfe7c29f1d350e85c4cd8122f6fe7e07c60f56f', onSlotchange: this.syncSlots }))), index.h("footer", { key: '56e115b1e6c46fc5de7a8df9ea005011787ecd51', class: { footer: true, 'is-empty': !this.hasActions }, part: "footer" }, index.h("slot", { key: 'd07e312d8486a7b761856f5137f24ab95a98c582', name: "actions", onSlotchange: this.syncSlots }))));
369
+ }
370
+ get host() { return index.getElement(this); }
371
+ };
372
+ RkRecipeCard.style = rkRecipeCardCss();
373
+
374
+ 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}}`;
375
+
376
+ const RkSearchBar = class {
377
+ constructor(hostRef) {
378
+ index.registerInstance(this, hostRef);
379
+ this.rkSearch = index.createEvent(this, "rkSearch");
380
+ this.rkClear = index.createEvent(this, "rkClear");
381
+ /** Current text in the field. Two-way: updated as the user types. */
382
+ this.value = '';
383
+ /** Placeholder shown when the field is empty. */
384
+ this.placeholder = 'Search recipes, ingredients, cuisines…';
385
+ /** Accessible label for the input. */
386
+ this.label = 'Search recipes';
387
+ /** Milliseconds to wait after the last keystroke before emitting `rkSearch`. */
388
+ this.debounce = 350;
389
+ /** Disables the whole control. */
390
+ this.disabled = false;
391
+ /** Shows a busy indicator (e.g. while the app is fetching results). */
392
+ this.loading = false;
393
+ this.focused = false;
394
+ this.handleInput = (event) => {
395
+ const next = event.target.value;
396
+ this.value = next;
397
+ clearTimeout(this.timer);
398
+ this.timer = setTimeout(() => this.emit(next), Math.max(0, this.debounce));
399
+ };
400
+ this.handleKeyDown = (event) => {
401
+ if (event.key === 'Enter') {
402
+ event.preventDefault();
403
+ this.emit(this.value);
404
+ }
405
+ else if (event.key === 'Escape' && this.value) {
406
+ event.preventDefault();
407
+ this.handleClear();
408
+ }
409
+ };
410
+ this.handleClear = () => {
411
+ clearTimeout(this.timer);
412
+ this.value = '';
413
+ if (this.inputEl)
414
+ this.inputEl.value = '';
415
+ this.rkClear.emit();
416
+ this.rkSearch.emit('');
417
+ this.inputEl?.focus();
418
+ };
419
+ }
420
+ onValueChange(next, prev) {
421
+ // Keep the DOM input in sync when the value is driven from the outside.
422
+ if (this.inputEl && this.inputEl.value !== next && next !== prev) {
423
+ this.inputEl.value = next ?? '';
424
+ }
425
+ }
426
+ disconnectedCallback() {
427
+ clearTimeout(this.timer);
428
+ }
429
+ /** Programmatically focus the input. */
430
+ async setFocus() {
431
+ this.inputEl?.focus();
432
+ }
433
+ emit(query) {
434
+ clearTimeout(this.timer);
435
+ this.rkSearch.emit(query.trim());
436
+ }
437
+ render() {
438
+ const hasValue = (this.value ?? '').length > 0;
439
+ return (index.h(index.Host, { key: 'cf58faf063a6c13bada8f5713f0dcbb582962577' }, index.h("div", { key: '13bdb192a7cb4fd699ffae8a9f67fba3e1a68c16', class: { shell: true, 'is-focused': this.focused, 'is-disabled': this.disabled }, part: "shell" }, index.h("span", { key: 'fe65bb803f4e3c01c1bf1eab66ab70658256820e', class: "icon", "aria-hidden": "true" }, index.h("svg", { key: 'a20ca316e086189ad00f8e9fed05cfa21c43fa10', viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round" }, index.h("circle", { key: '8298c51dc269224356a1e3248838e2ff8c20aab3', cx: "11", cy: "11", r: "7" }), index.h("path", { key: '92acd9c5a363ad4a9e9f14cbcf6b4ff86aae2bf2', d: "m20 20-3.5-3.5" }))), index.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 && index.h("span", { key: '17c0e5483cc3bea79642192c6cb8548f32071f02', class: "spinner", part: "spinner", "aria-hidden": "true" }), hasValue && !this.loading && (index.h("button", { key: '7df966f93f1de6a1d3a380c4856f235a671521ad', class: "clear", part: "clear", type: "button", onClick: this.handleClear, "aria-label": "Clear search", disabled: this.disabled }, index.h("svg", { key: '29457188a630e71e4c29f93939edbcb28862cade', viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "aria-hidden": "true" }, index.h("path", { key: '64406c87eaaa582151fdf5d1ee7929a6f311cc4c', d: "M6 6l12 12M18 6 6 18" })))), index.h("span", { key: '7abf5bd0cdb2d15b7143491c5de5216df872deff', class: "actions", part: "actions" }, index.h("slot", { key: '09fa60e54e72af6c9e7187f3406a11e80d5a3004', name: "actions" }))), this.hint && (index.h("p", { key: '1dd826505bcfc2cb5b7712adc18e7e29dc814f2b', class: "hint", part: "hint" }, this.hint))));
440
+ }
441
+ static get watchers() { return {
442
+ "value": [{
443
+ "onValueChange": 0
444
+ }]
445
+ }; }
446
+ };
447
+ RkSearchBar.style = rkSearchBarCss();
448
+
449
+ 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}}`;
450
+
451
+ const RkSegmentedControl = class {
452
+ constructor(hostRef) {
453
+ index.registerInstance(this, hostRef);
454
+ this.rkSegmentChange = index.createEvent(this, "rkSegmentChange");
455
+ /** The available segments. Accepts an array or a JSON-encoded string. */
456
+ this.options = [];
457
+ /** Accessible label for the group. */
458
+ this.label = 'View options';
459
+ /** Stretches the control to fill its container. */
460
+ this.stretch = false;
461
+ this.parsed = [];
462
+ this.handleKeyDown = (event, index) => {
463
+ const step = event.key === 'ArrowRight' ? 1 : event.key === 'ArrowLeft' ? -1 : 0;
464
+ if (step === 0)
465
+ return;
466
+ event.preventDefault();
467
+ const items = this.parsed.filter(option => !option.disabled);
468
+ if (items.length === 0)
469
+ return;
470
+ const currentIndex = items.indexOf(this.parsed[index]);
471
+ const next = items[(currentIndex + step + items.length) % items.length];
472
+ this.select(next);
473
+ };
474
+ }
475
+ onOptionsChange() {
476
+ this.parseOptions();
477
+ }
478
+ componentWillLoad() {
479
+ this.parseOptions();
480
+ }
481
+ /**
482
+ * Attributes can only carry strings, so a JSON payload is accepted as a
483
+ * fallback for consumers that cannot set DOM properties.
484
+ */
485
+ parseOptions() {
486
+ if (Array.isArray(this.options)) {
487
+ this.parsed = this.options;
488
+ return;
489
+ }
490
+ if (typeof this.options === 'string' && this.options.trim()) {
491
+ try {
492
+ const decoded = JSON.parse(this.options);
493
+ this.parsed = Array.isArray(decoded) ? decoded : [];
494
+ }
495
+ catch {
496
+ console.warn('[rk-segmented-control] `options` is not valid JSON:', this.options);
497
+ this.parsed = [];
498
+ }
499
+ return;
500
+ }
501
+ this.parsed = [];
502
+ }
503
+ select(option) {
504
+ if (option.disabled || option.value === this.value)
505
+ return;
506
+ this.value = option.value;
507
+ this.rkSegmentChange.emit(option.value);
508
+ }
509
+ render() {
510
+ return (index.h("div", { key: 'e2b6eec632ebe51b9f259c60b174126de1a9521c', class: { group: true, 'is-stretch': this.stretch }, part: "group", role: "tablist", "aria-label": this.label }, this.parsed.map((option, index$1) => {
511
+ const selected = option.value === this.value;
512
+ return (index.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$1) }, option.label, typeof option.count === 'number' && index.h("span", { class: "count" }, option.count)));
513
+ })));
514
+ }
515
+ static get watchers() { return {
516
+ "options": [{
517
+ "onOptionsChange": 0
518
+ }]
519
+ }; }
520
+ };
521
+ RkSegmentedControl.style = rkSegmentedControlCss();
522
+
523
+ 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}}`;
524
+
525
+ const RkTagInput = class {
526
+ constructor(hostRef) {
527
+ index.registerInstance(this, hostRef);
528
+ this.rkTagsChange = index.createEvent(this, "rkTagsChange");
529
+ /** Current tags. Array, JSON string, or comma-separated string. */
530
+ this.values = [];
531
+ /** Placeholder for the entry field. */
532
+ this.placeholder = 'Add a tag and press Enter';
533
+ /** Maximum number of tags. `0` means unlimited. */
534
+ this.maxTags = 0;
535
+ /** Prevent editing. */
536
+ this.disabled = false;
537
+ this.draft = '';
538
+ this.tags = [];
539
+ this.handleKeyDown = (event) => {
540
+ if (event.key === 'Enter' || event.key === ',') {
541
+ event.preventDefault();
542
+ this.addDraft();
543
+ }
544
+ else if (event.key === 'Backspace' && this.draft === '' && this.tags.length > 0) {
545
+ event.preventDefault();
546
+ this.removeAt(this.tags.length - 1);
547
+ }
548
+ };
549
+ }
550
+ onValuesChange() {
551
+ this.syncTags();
552
+ }
553
+ componentWillLoad() {
554
+ this.syncTags();
555
+ }
556
+ syncTags() {
557
+ const incoming = this.values;
558
+ if (Array.isArray(incoming)) {
559
+ this.tags = incoming.filter(tag => typeof tag === 'string');
560
+ return;
561
+ }
562
+ if (typeof incoming === 'string' && incoming.trim()) {
563
+ const trimmed = incoming.trim();
564
+ if (trimmed.startsWith('[')) {
565
+ try {
566
+ const decoded = JSON.parse(trimmed);
567
+ this.tags = Array.isArray(decoded) ? decoded.map(String) : [];
568
+ return;
569
+ }
570
+ catch {
571
+ /* fall through to comma parsing */
572
+ }
573
+ }
574
+ this.tags = trimmed
575
+ .split(',')
576
+ .map(tag => tag.trim())
577
+ .filter(Boolean);
578
+ return;
579
+ }
580
+ this.tags = [];
581
+ }
582
+ get atCapacity() {
583
+ return this.maxTags > 0 && this.tags.length >= this.maxTags;
584
+ }
585
+ commit(next) {
586
+ this.tags = next;
587
+ this.rkTagsChange.emit([...next]);
588
+ }
589
+ addDraft() {
590
+ const candidates = this.draft
591
+ .split(',')
592
+ .map(tag => tag.trim())
593
+ .filter(Boolean);
594
+ if (candidates.length === 0)
595
+ return;
596
+ const next = [...this.tags];
597
+ for (const candidate of candidates) {
598
+ if (this.maxTags > 0 && next.length >= this.maxTags)
599
+ break;
600
+ // Case-insensitive de-duplication keeps "Vegan" and "vegan" from coexisting.
601
+ if (next.some(tag => tag.toLowerCase() === candidate.toLowerCase()))
602
+ continue;
603
+ next.push(candidate);
604
+ }
605
+ this.draft = '';
606
+ if (next.length !== this.tags.length)
607
+ this.commit(next);
608
+ }
609
+ removeAt(index) {
610
+ this.commit(this.tags.filter((_, position) => position !== index));
611
+ }
612
+ render() {
613
+ return (index.h(index.Host, { key: '446657dc603ffa9e4d14ee5c54c947aef90bab4f' }, this.label && (index.h("label", { key: '37575fae490ba8e1a03a3aa6e25ba46d2b364d9e', class: "label", part: "label", onClick: () => this.inputEl?.focus() }, this.label, this.maxTags > 0 && (index.h("span", { key: 'e29c5d1fd9038cee4707c310adfb88ce7cc75308', class: "counter" }, this.tags.length, "/", this.maxTags)))), index.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$1) => (index.h("span", { class: "tag", part: "tag", key: `${tag}-${index$1}` }, index.h("span", { class: "tag-text" }, tag), index.h("button", { class: "tag-remove", type: "button", "aria-label": `Remove ${tag}`, disabled: this.disabled, onClick: event => {
614
+ event.stopPropagation();
615
+ this.removeAt(index$1);
616
+ } }, index.h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2.6", "stroke-linecap": "round", "aria-hidden": "true" }, index.h("path", { d: "M6 6l12 12M18 6 6 18" })))))), index.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 && (index.h("p", { key: '31e84622ac580903e7ebb6af7f57b1842572ce9f', class: "error", part: "error", role: "alert" }, this.error))));
617
+ }
618
+ static get watchers() { return {
619
+ "values": [{
620
+ "onValuesChange": 0
621
+ }]
622
+ }; }
623
+ };
624
+ RkTagInput.style = rkTagInputCss();
625
+
626
+ exports.rk_badge = RkBadge;
627
+ exports.rk_chip = RkChip;
628
+ exports.rk_empty_state = RkEmptyState;
629
+ exports.rk_meal_slot = RkMealSlot;
630
+ exports.rk_modal = RkModal;
631
+ exports.rk_rating = RkRating;
632
+ exports.rk_recipe_card = RkRecipeCard;
633
+ exports.rk_search_bar = RkSearchBar;
634
+ exports.rk_segmented_control = RkSegmentedControl;
635
+ exports.rk_tag_input = RkTagInput;
636
+ //# 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.cjs.js.map
637
+
638
+ //# sourceMappingURL=rk-badge_10.cjs.entry.js.map