@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
@@ -1,181 +0,0 @@
1
- import { Component, Event, EventEmitter, h, Host, Prop, State, Watch } from '@stencil/core';
2
-
3
- /**
4
- * A tag / token editor used for recipe tags and free-form ingredient lists.
5
- *
6
- * `values` is an array property — the SvelteKit app assigns it as a DOM
7
- * property. A JSON-encoded or comma-separated string attribute also works.
8
- */
9
- @Component({
10
- tag: 'rk-tag-input',
11
- styleUrl: 'rk-tag-input.css',
12
- shadow: true,
13
- })
14
- export class RkTagInput {
15
- /** Current tags. Array, JSON string, or comma-separated string. */
16
- @Prop() values: string[] | string = [];
17
-
18
- /** Placeholder for the entry field. */
19
- @Prop() placeholder = 'Add a tag and press Enter';
20
-
21
- /** Visible field label. */
22
- @Prop() label?: string;
23
-
24
- /** Maximum number of tags. `0` means unlimited. */
25
- @Prop() maxTags = 0;
26
-
27
- /** Prevent editing. */
28
- @Prop() disabled = false;
29
-
30
- /** Validation message rendered under the field. */
31
- @Prop() error?: string;
32
-
33
- /** Emitted with the full, updated list whenever tags change. */
34
- @Event({ eventName: 'rkTagsChange' }) rkTagsChange!: EventEmitter<string[]>;
35
-
36
- @State() private draft = '';
37
- @State() private tags: string[] = [];
38
-
39
- private inputEl?: HTMLInputElement;
40
-
41
- @Watch('values')
42
- protected onValuesChange() {
43
- this.syncTags();
44
- }
45
-
46
- componentWillLoad() {
47
- this.syncTags();
48
- }
49
-
50
- private syncTags() {
51
- const incoming = this.values;
52
- if (Array.isArray(incoming)) {
53
- this.tags = incoming.filter(tag => typeof tag === 'string');
54
- return;
55
- }
56
- if (typeof incoming === 'string' && incoming.trim()) {
57
- const trimmed = incoming.trim();
58
- if (trimmed.startsWith('[')) {
59
- try {
60
- const decoded = JSON.parse(trimmed);
61
- this.tags = Array.isArray(decoded) ? decoded.map(String) : [];
62
- return;
63
- } catch {
64
- /* fall through to comma parsing */
65
- }
66
- }
67
- this.tags = trimmed
68
- .split(',')
69
- .map(tag => tag.trim())
70
- .filter(Boolean);
71
- return;
72
- }
73
- this.tags = [];
74
- }
75
-
76
- private get atCapacity() {
77
- return this.maxTags > 0 && this.tags.length >= this.maxTags;
78
- }
79
-
80
- private commit(next: string[]) {
81
- this.tags = next;
82
- this.rkTagsChange.emit([...next]);
83
- }
84
-
85
- private addDraft() {
86
- const candidates = this.draft
87
- .split(',')
88
- .map(tag => tag.trim())
89
- .filter(Boolean);
90
- if (candidates.length === 0) return;
91
-
92
- const next = [...this.tags];
93
- for (const candidate of candidates) {
94
- if (this.maxTags > 0 && next.length >= this.maxTags) break;
95
- // Case-insensitive de-duplication keeps "Vegan" and "vegan" from coexisting.
96
- if (next.some(tag => tag.toLowerCase() === candidate.toLowerCase())) continue;
97
- next.push(candidate);
98
- }
99
-
100
- this.draft = '';
101
- if (next.length !== this.tags.length) this.commit(next);
102
- }
103
-
104
- private removeAt(index: number) {
105
- this.commit(this.tags.filter((_, position) => position !== index));
106
- }
107
-
108
- private handleKeyDown = (event: KeyboardEvent) => {
109
- if (event.key === 'Enter' || event.key === ',') {
110
- event.preventDefault();
111
- this.addDraft();
112
- } else if (event.key === 'Backspace' && this.draft === '' && this.tags.length > 0) {
113
- event.preventDefault();
114
- this.removeAt(this.tags.length - 1);
115
- }
116
- };
117
-
118
- render() {
119
- return (
120
- <Host>
121
- {this.label && (
122
- <label class="label" part="label" onClick={() => this.inputEl?.focus()}>
123
- {this.label}
124
- {this.maxTags > 0 && (
125
- <span class="counter">
126
- {this.tags.length}/{this.maxTags}
127
- </span>
128
- )}
129
- </label>
130
- )}
131
-
132
- <div
133
- class={{ shell: true, 'is-disabled': this.disabled, 'has-error': !!this.error }}
134
- part="shell"
135
- onClick={() => !this.disabled && this.inputEl?.focus()}
136
- >
137
- {this.tags.map((tag, index) => (
138
- <span class="tag" part="tag" key={`${tag}-${index}`}>
139
- <span class="tag-text">{tag}</span>
140
- <button
141
- class="tag-remove"
142
- type="button"
143
- aria-label={`Remove ${tag}`}
144
- disabled={this.disabled}
145
- onClick={event => {
146
- event.stopPropagation();
147
- this.removeAt(index);
148
- }}
149
- >
150
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" aria-hidden="true">
151
- <path d="M6 6l12 12M18 6 6 18" />
152
- </svg>
153
- </button>
154
- </span>
155
- ))}
156
-
157
- <input
158
- ref={el => (this.inputEl = el)}
159
- class="entry"
160
- part="input"
161
- type="text"
162
- value={this.draft}
163
- placeholder={this.tags.length === 0 ? this.placeholder : this.atCapacity ? '' : 'Add another…'}
164
- aria-label={this.label ?? 'Add a tag'}
165
- disabled={this.disabled || this.atCapacity}
166
- autocomplete="off"
167
- onInput={event => (this.draft = (event.target as HTMLInputElement).value)}
168
- onKeyDown={this.handleKeyDown}
169
- onBlur={() => this.addDraft()}
170
- />
171
- </div>
172
-
173
- {this.error && (
174
- <p class="error" part="error" role="alert">
175
- {this.error}
176
- </p>
177
- )}
178
- </Host>
179
- );
180
- }
181
- }
@@ -1,76 +0,0 @@
1
- import { newSpecPage } from '@stencil/core/testing';
2
- import { RkTagInput } from '../rk-tag-input';
3
-
4
- describe('rk-tag-input', () => {
5
- async function setup(html = `<rk-tag-input></rk-tag-input>`) {
6
- const page = await newSpecPage({ components: [RkTagInput], html });
7
- const input = page.root.shadowRoot.querySelector('input') as HTMLInputElement;
8
- const onChange = jest.fn();
9
- page.root.addEventListener('rkTagsChange', onChange);
10
- return { page, input, onChange };
11
- }
12
-
13
- async function enter(page: Awaited<ReturnType<typeof newSpecPage>>, input: HTMLInputElement, value: string) {
14
- input.value = value;
15
- input.dispatchEvent(new Event('input'));
16
- input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
17
- await page.waitForChanges();
18
- }
19
-
20
- it('adds a tag on Enter and emits the whole list', async () => {
21
- const { page, input, onChange } = await setup();
22
-
23
- await enter(page, input, 'vegan');
24
- expect(onChange.mock.calls[0][0].detail).toEqual(['vegan']);
25
-
26
- await enter(page, input, 'one pot');
27
- expect(onChange.mock.calls[1][0].detail).toEqual(['vegan', 'one pot']);
28
- });
29
-
30
- it('splits a comma-separated entry into several tags', async () => {
31
- const { page, input, onChange } = await setup();
32
-
33
- await enter(page, input, 'quick, easy, spicy');
34
- expect(onChange.mock.calls[0][0].detail).toEqual(['quick', 'easy', 'spicy']);
35
- });
36
-
37
- it('rejects case-insensitive duplicates', async () => {
38
- const { page, input, onChange } = await setup();
39
-
40
- await enter(page, input, 'Vegan');
41
- await enter(page, input, 'vegan');
42
-
43
- expect(onChange).toHaveBeenCalledTimes(1);
44
- expect(page.root.shadowRoot.querySelectorAll('.tag')).toHaveLength(1);
45
- });
46
-
47
- it('stops accepting tags at maxTags', async () => {
48
- const { page, input, onChange } = await setup(`<rk-tag-input max-tags="2"></rk-tag-input>`);
49
-
50
- await enter(page, input, 'a');
51
- await enter(page, input, 'b');
52
- expect(onChange).toHaveBeenCalledTimes(2);
53
-
54
- expect(input.disabled).toBe(true);
55
- expect(page.root.shadowRoot.querySelectorAll('.tag')).toHaveLength(2);
56
- });
57
-
58
- it('accepts a JSON string attribute as a fallback for the array property', async () => {
59
- const page = await newSpecPage({
60
- components: [RkTagInput],
61
- html: `<rk-tag-input values='["comfort food","one pot"]'></rk-tag-input>`,
62
- });
63
-
64
- const labels = Array.from(page.root.shadowRoot.querySelectorAll('.tag-text')).map(node => node.textContent);
65
- expect(labels).toEqual(['comfort food', 'one pot']);
66
- });
67
-
68
- it('removes the last tag on Backspace when the field is empty', async () => {
69
- const { page, input, onChange } = await setup(`<rk-tag-input values='["a","b"]'></rk-tag-input>`);
70
-
71
- input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Backspace' }));
72
- await page.waitForChanges();
73
-
74
- expect(onChange.mock.calls[0][0].detail).toEqual(['a']);
75
- });
76
- });
@@ -1,259 +0,0 @@
1
- <!doctype html>
2
- <html lang="en" dir="ltr">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>recipe-kit-stencil — component gallery</title>
7
- <script type="module" src="/build/recipe-kit.esm.js"></script>
8
- <script nomodule src="/build/recipe-kit.js"></script>
9
- <style>
10
- body {
11
- margin: 0;
12
- padding: 40px 24px 96px;
13
- background: var(--rk-bg);
14
- color: var(--rk-ink);
15
- font-family: var(--rk-font);
16
- font-size: 15px;
17
- }
18
- main {
19
- max-width: 960px;
20
- margin: 0 auto;
21
- display: grid;
22
- gap: 44px;
23
- }
24
- h1 {
25
- font-family: var(--rk-font-display);
26
- font-size: 2.1rem;
27
- margin: 0 0 6px;
28
- letter-spacing: -0.02em;
29
- }
30
- .lede {
31
- color: var(--rk-muted);
32
- margin: 0;
33
- max-width: 60ch;
34
- line-height: 1.6;
35
- }
36
- section h2 {
37
- font-size: 0.78rem;
38
- text-transform: uppercase;
39
- letter-spacing: 0.09em;
40
- color: var(--rk-muted);
41
- margin: 0 0 14px;
42
- padding-bottom: 8px;
43
- border-bottom: 1px solid var(--rk-border);
44
- }
45
- .row {
46
- display: flex;
47
- flex-wrap: wrap;
48
- gap: 12px;
49
- align-items: center;
50
- }
51
- .grid {
52
- display: grid;
53
- grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
54
- gap: 18px;
55
- }
56
- .planner {
57
- display: grid;
58
- grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
59
- gap: 12px;
60
- }
61
- button.demo {
62
- font: inherit;
63
- font-weight: 600;
64
- padding: 9px 16px;
65
- border-radius: 999px;
66
- border: 1px solid var(--rk-border);
67
- background: var(--rk-surface);
68
- color: var(--rk-ink);
69
- cursor: pointer;
70
- }
71
- #log {
72
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
73
- font-size: 12px;
74
- background: var(--rk-ink);
75
- color: #d8f5c8;
76
- padding: 14px 16px;
77
- border-radius: 12px;
78
- max-height: 180px;
79
- overflow: auto;
80
- white-space: pre-wrap;
81
- margin: 0;
82
- }
83
- </style>
84
- </head>
85
- <body>
86
- <main>
87
- <header>
88
- <h1>recipe-kit-stencil</h1>
89
- <p class="lede">
90
- A framework-agnostic web component library built with StencilJS. Every event below is logged at the bottom of the page — the same events the
91
- SvelteKit application listens for.
92
- </p>
93
- </header>
94
-
95
- <section>
96
- <h2>rk-search-bar</h2>
97
- <rk-search-bar hint="Debounced at 350&thinsp;ms. Press Escape to clear.">
98
- <rk-badge slot="actions" tone="accent">beta</rk-badge>
99
- </rk-search-bar>
100
- </section>
101
-
102
- <section>
103
- <h2>rk-chip &amp; rk-segmented-control</h2>
104
- <div class="row">
105
- <rk-chip label="Seafood" value="Seafood" count="18"><span>🐟</span></rk-chip>
106
- <rk-chip label="Dessert" value="Dessert" selected tone="primary"></rk-chip>
107
- <rk-chip label="Vegan" value="Vegan" tone="accent" selected></rk-chip>
108
- <rk-chip label="Italian" value="Italian" dismissible></rk-chip>
109
- </div>
110
- <br />
111
- <rk-segmented-control id="segments" label="Sort recipes"></rk-segmented-control>
112
- </section>
113
-
114
- <section>
115
- <h2>rk-badge &amp; rk-rating</h2>
116
- <div class="row">
117
- <rk-badge>neutral</rk-badge>
118
- <rk-badge tone="primary" variant="solid">primary solid</rk-badge>
119
- <rk-badge tone="accent" variant="outline">accent outline</rk-badge>
120
- <rk-badge tone="success" small>30 min</rk-badge>
121
- <rk-rating value="4" show-value count="128"></rk-rating>
122
- <rk-rating value="3.5" readonly show-value></rk-rating>
123
- </div>
124
- </section>
125
-
126
- <section>
127
- <h2>rk-recipe-card</h2>
128
- <div class="grid">
129
- <rk-recipe-card
130
- recipe-id="52772"
131
- name="Teriyaki Chicken Casserole"
132
- image="https://www.themealdb.com/images/media/meals/wvpsxx1468256321.jpg"
133
- category="Chicken"
134
- area="Japanese"
135
- minutes="45"
136
- servings="4"
137
- href="#52772"
138
- >
139
- <rk-badge tone="accent" small>weeknight</rk-badge>
140
- <button class="demo" slot="actions">Add to plan</button>
141
- </rk-recipe-card>
142
-
143
- <rk-recipe-card
144
- recipe-id="local-1"
145
- name="Grandma's Sunday Ragù"
146
- category="Pasta"
147
- area="Italian"
148
- minutes="180"
149
- origin="custom"
150
- favorite
151
- >
152
- <rk-badge tone="warning" small>slow cook</rk-badge>
153
- </rk-recipe-card>
154
-
155
- <rk-recipe-card
156
- recipe-id="52959"
157
- name="Baked Salmon with Fennel &amp; Tomatoes"
158
- image="https://www.themealdb.com/images/media/meals/1548772327.jpg"
159
- category="Seafood"
160
- area="British"
161
- minutes="30"
162
- compact
163
- ></rk-recipe-card>
164
- </div>
165
- </section>
166
-
167
- <section>
168
- <h2>rk-meal-slot</h2>
169
- <div class="planner">
170
- <rk-meal-slot day="monday" meal-type="breakfast" meal-label="Breakfast"></rk-meal-slot>
171
- <rk-meal-slot
172
- day="monday"
173
- meal-type="dinner"
174
- meal-label="Dinner"
175
- recipe-id="52772"
176
- recipe-name="Teriyaki Chicken Casserole"
177
- recipe-image="https://www.themealdb.com/images/media/meals/wvpsxx1468256321.jpg"
178
- ></rk-meal-slot>
179
- <rk-meal-slot day="tuesday" meal-type="lunch" meal-label="Lunch"></rk-meal-slot>
180
- </div>
181
- </section>
182
-
183
- <section>
184
- <h2>rk-tag-input</h2>
185
- <rk-tag-input id="tags" label="Recipe tags" max-tags="6"></rk-tag-input>
186
- </section>
187
-
188
- <section>
189
- <h2>rk-modal</h2>
190
- <button class="demo" id="open-modal">Open dialog</button>
191
- <rk-modal id="modal" heading="Delete this recipe?" subheading="This cannot be undone.">
192
- <p style="margin: 0">Removing “Grandma's Sunday Ragù” also removes it from every day of your weekly plan.</p>
193
- <button class="demo" slot="footer" id="cancel-modal">Cancel</button>
194
- <button class="demo" slot="footer" style="background: var(--rk-danger); border-color: var(--rk-danger); color: #fff">Delete</button>
195
- </rk-modal>
196
- </section>
197
-
198
- <section>
199
- <h2>rk-empty-state</h2>
200
- <rk-empty-state icon="🔍" heading="No recipes matched “quinoa risotto”" message="Try a broader search term, or clear the active filters.">
201
- <button class="demo">Clear filters</button>
202
- </rk-empty-state>
203
- </section>
204
-
205
- <section>
206
- <h2>Event log</h2>
207
- <pre id="log">Interact with the components above…</pre>
208
- </section>
209
- </main>
210
-
211
- <script type="module">
212
- const log = document.getElementById('log');
213
- let lines = [];
214
- const record = (event) => {
215
- lines.unshift(`${event.type} → ${JSON.stringify(event.detail)}`);
216
- log.textContent = lines.slice(0, 40).join('\n');
217
- };
218
-
219
- const events = [
220
- 'rkSearch', 'rkClear', 'rkChipToggle', 'rkChipDismiss', 'rkSegmentChange', 'rkRatingChange',
221
- 'rkCardSelect', 'rkFavoriteToggle', 'rkMealAdd', 'rkMealSwap', 'rkMealRemove', 'rkMealOpen',
222
- 'rkMealDrop', 'rkTagsChange', 'rkModalClose',
223
- ];
224
- events.forEach((name) => document.addEventListener(name, record));
225
-
226
- // Array-valued properties are assigned as DOM properties, never attributes.
227
- customElements.whenDefined('rk-segmented-control').then(() => {
228
- const segments = document.getElementById('segments');
229
- segments.options = [
230
- { label: 'Relevance', value: 'relevance' },
231
- { label: 'A–Z', value: 'az' },
232
- { label: 'Quickest', value: 'time', count: 12 },
233
- { label: 'Unavailable', value: 'x', disabled: true },
234
- ];
235
- segments.value = 'relevance';
236
- segments.addEventListener('rkSegmentChange', (event) => (segments.value = event.detail));
237
- });
238
-
239
- customElements.whenDefined('rk-tag-input').then(() => {
240
- const tags = document.getElementById('tags');
241
- tags.values = ['comfort food', 'one pot'];
242
- });
243
-
244
- // Favorite state is owned by the host app, exactly as in SvelteKit.
245
- document.addEventListener('rkFavoriteToggle', (event) => {
246
- event.target.favorite = event.detail.favorite;
247
- });
248
-
249
- document.addEventListener('rkChipToggle', (event) => {
250
- event.target.selected = event.detail.selected;
251
- });
252
-
253
- const modal = document.getElementById('modal');
254
- document.getElementById('open-modal').addEventListener('click', () => (modal.open = true));
255
- document.getElementById('cancel-modal').addEventListener('click', () => (modal.open = false));
256
- modal.addEventListener('rkModalClose', () => (modal.open = false));
257
- </script>
258
- </body>
259
- </html>
@@ -1,41 +0,0 @@
1
- import { Config } from '@stencil/core';
2
-
3
- export const config: Config = {
4
- namespace: 'recipe-kit',
5
- globalStyle: 'src/global/rk-global.css',
6
- sourceMap: true,
7
- buildEs5: false,
8
- extras: {
9
- enableImportInjection: true,
10
- },
11
- outputTargets: [
12
- // Lazy-loaded distribution + `loader/` entry point. This is what the
13
- // SvelteKit app consumes from npm via `recipe-kit-stencil/loader`.
14
- {
15
- type: 'dist',
16
- esmLoaderPath: '../loader',
17
- isPrimaryPackageOutputTarget: true,
18
- copy: [{ src: 'global/rk-global.css', dest: 'rk-global.css', warn: true }],
19
- },
20
- // Standalone custom elements, for consumers that want to import a single
21
- // component and define it themselves (tree-shakeable).
22
- {
23
- type: 'dist-custom-elements',
24
- customElementsExportBehavior: 'auto-define-custom-elements',
25
- externalRuntime: false,
26
- generateTypeDeclarations: true,
27
- },
28
- {
29
- type: 'docs-readme',
30
- footer: '*Built with [StencilJS](https://stenciljs.com/)*',
31
- },
32
- {
33
- type: 'www',
34
- serviceWorker: null,
35
- },
36
- ],
37
- testing: {
38
- browserHeadless: 'shell',
39
- },
40
- validatePrimaryPackageOutputTarget: true,
41
- };
@@ -1,24 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "allowSyntheticDefaultImports": true,
4
- "allowUnreachableCode": false,
5
- "declaration": false,
6
- "experimentalDecorators": true,
7
- "lib": ["dom", "es2020"],
8
- "moduleResolution": "bundler",
9
- "module": "esnext",
10
- "target": "es2020",
11
- "noUnusedLocals": true,
12
- "noUnusedParameters": true,
13
- "sourceMap": true,
14
- "strictNullChecks": false,
15
- "strictPropertyInitialization": false,
16
- "jsx": "react",
17
- "jsxFactory": "h",
18
- "jsxFragmentFactory": "Fragment",
19
- "esModuleInterop": true,
20
- "skipLibCheck": true
21
- },
22
- "include": ["src"],
23
- "exclude": ["node_modules", "dist", "loader", "www"]
24
- }