@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.
- package/README.md +108 -277
- package/dist/cjs/index-BEr8BsSA.js +1712 -0
- package/dist/cjs/index-BEr8BsSA.js.map +1 -0
- package/dist/cjs/index.cjs.js +5 -0
- package/dist/cjs/index.cjs.js.map +1 -0
- package/dist/cjs/loader.cjs.js +15 -0
- package/dist/cjs/loader.cjs.js.map +1 -0
- package/dist/cjs/recipe-kit.cjs.js +27 -0
- package/dist/cjs/recipe-kit.cjs.js.map +1 -0
- package/dist/cjs/rk-badge_10.cjs.entry.js +638 -0
- package/dist/cjs/rk-badge_10.cjs.entry.js.map +1 -0
- package/dist/collection/collection-manifest.json +22 -0
- package/dist/collection/components/rk-badge/rk-badge.js +97 -0
- package/dist/collection/components/rk-badge/rk-badge.js.map +1 -0
- package/dist/collection/components/rk-chip/rk-chip.js +245 -0
- package/dist/collection/components/rk-chip/rk-chip.js.map +1 -0
- package/dist/collection/components/rk-empty-state/rk-empty-state.js +138 -0
- package/dist/collection/components/rk-empty-state/rk-empty-state.js.map +1 -0
- package/dist/collection/components/rk-meal-slot/rk-meal-slot.js +312 -0
- package/dist/collection/components/rk-meal-slot/rk-meal-slot.js.map +1 -0
- package/dist/collection/components/rk-modal/rk-modal.js +256 -0
- package/dist/collection/components/rk-modal/rk-modal.js.map +1 -0
- package/dist/collection/components/rk-rating/rk-rating.js +228 -0
- package/dist/collection/components/rk-rating/rk-rating.js.map +1 -0
- package/dist/collection/components/rk-recipe-card/rk-recipe-card.js +366 -0
- package/dist/collection/components/rk-recipe-card/rk-recipe-card.js.map +1 -0
- package/dist/collection/components/rk-search-bar/rk-search-bar.js +291 -0
- package/dist/collection/components/rk-search-bar/rk-search-bar.js.map +1 -0
- package/dist/collection/components/rk-segmented-control/rk-segmented-control.js +196 -0
- package/dist/collection/components/rk-segmented-control/rk-segmented-control.js.map +1 -0
- package/dist/collection/components/rk-tag-input/rk-tag-input.js +264 -0
- package/dist/collection/components/rk-tag-input/rk-tag-input.js.map +1 -0
- package/dist/collection/index.js +2 -0
- package/dist/collection/index.js.map +1 -0
- package/dist/components/index.d.ts +35 -0
- package/dist/components/index.js +2 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/rk-badge.d.ts +11 -0
- package/dist/components/rk-badge.js +2 -0
- package/dist/components/rk-badge.js.map +1 -0
- package/dist/components/rk-chip.d.ts +11 -0
- package/dist/components/rk-chip.js +2 -0
- package/dist/components/rk-chip.js.map +1 -0
- package/dist/components/rk-empty-state.d.ts +11 -0
- package/dist/components/rk-empty-state.js +2 -0
- package/dist/components/rk-empty-state.js.map +1 -0
- package/dist/components/rk-meal-slot.d.ts +11 -0
- package/dist/components/rk-meal-slot.js +2 -0
- package/dist/components/rk-meal-slot.js.map +1 -0
- package/dist/components/rk-modal.d.ts +11 -0
- package/dist/components/rk-modal.js +2 -0
- package/dist/components/rk-modal.js.map +1 -0
- package/dist/components/rk-rating.d.ts +11 -0
- package/dist/components/rk-rating.js +2 -0
- package/dist/components/rk-rating.js.map +1 -0
- package/dist/components/rk-recipe-card.d.ts +11 -0
- package/dist/components/rk-recipe-card.js +2 -0
- package/dist/components/rk-recipe-card.js.map +1 -0
- package/dist/components/rk-search-bar.d.ts +11 -0
- package/dist/components/rk-search-bar.js +2 -0
- package/dist/components/rk-search-bar.js.map +1 -0
- package/dist/components/rk-segmented-control.d.ts +11 -0
- package/dist/components/rk-segmented-control.js +2 -0
- package/dist/components/rk-segmented-control.js.map +1 -0
- package/dist/components/rk-tag-input.d.ts +11 -0
- package/dist/components/rk-tag-input.js +2 -0
- package/dist/components/rk-tag-input.js.map +1 -0
- package/dist/esm/index-Bxo0C8lB.js +1684 -0
- package/dist/esm/index-Bxo0C8lB.js.map +1 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/loader.js +13 -0
- package/dist/esm/loader.js.map +1 -0
- package/dist/esm/recipe-kit.js +23 -0
- package/dist/esm/recipe-kit.js.map +1 -0
- package/dist/esm/rk-badge_10.entry.js +627 -0
- package/dist/esm/rk-badge_10.entry.js.map +1 -0
- package/dist/index.cjs.js +1 -0
- package/dist/index.js +1 -0
- package/dist/recipe-kit/index.esm.js +2 -0
- package/dist/recipe-kit/index.esm.js.map +1 -0
- package/dist/recipe-kit/p-Bxo0C8lB.js +3 -0
- package/dist/recipe-kit/p-Bxo0C8lB.js.map +1 -0
- package/dist/recipe-kit/p-fcb955c7.entry.js +2 -0
- package/dist/recipe-kit/p-fcb955c7.entry.js.map +1 -0
- package/dist/recipe-kit/recipe-kit.css +1 -0
- package/dist/recipe-kit/recipe-kit.esm.js +2 -0
- package/dist/recipe-kit/recipe-kit.esm.js.map +1 -0
- package/dist/recipe-kit/rk-global.css +104 -0
- package/dist/types/components/rk-badge/rk-badge.d.ts +15 -0
- package/dist/types/components/rk-chip/rk-chip.d.ts +37 -0
- package/dist/types/components/rk-empty-state/rk-empty-state.d.ts +19 -0
- package/dist/types/components/rk-meal-slot/rk-meal-slot.d.ts +53 -0
- package/dist/types/components/rk-modal/rk-modal.d.ts +41 -0
- package/dist/types/components/rk-rating/rk-rating.d.ts +26 -0
- package/dist/types/components/rk-recipe-card/rk-recipe-card.d.ts +67 -0
- package/dist/types/components/rk-search-bar/rk-search-bar.d.ts +39 -0
- package/dist/types/components/rk-segmented-control/rk-segmented-control.d.ts +37 -0
- package/dist/types/components/rk-tag-input/rk-tag-input.d.ts +35 -0
- package/{packages/recipe-kit-stencil/src → dist/types}/components.d.ts +1 -1
- package/{packages/recipe-kit-stencil/src/index.ts → dist/types/index.d.ts} +0 -2
- package/dist/types/stencil-public-runtime.d.ts +1873 -0
- package/loader/cdn.js +1 -0
- package/loader/index.cjs.js +1 -0
- package/loader/index.d.ts +24 -0
- package/loader/index.es2017.js +1 -0
- package/loader/index.js +2 -0
- package/package.json +84 -22
- package/.claude/settings.local.json +0 -10
- package/.npmr +0 -1
- package/apps/recipe-web/package.json +0 -31
- package/apps/recipe-web/src/app.css +0 -418
- package/apps/recipe-web/src/app.d.ts +0 -32
- package/apps/recipe-web/src/app.html +0 -25
- package/apps/recipe-web/src/lib/api/mealdb.ts +0 -240
- package/apps/recipe-web/src/lib/components/AppHeader.svelte +0 -290
- package/apps/recipe-web/src/lib/components/ConfirmDialog.svelte +0 -46
- package/apps/recipe-web/src/lib/components/Modal.svelte +0 -33
- package/apps/recipe-web/src/lib/components/RecipeCard.svelte +0 -100
- package/apps/recipe-web/src/lib/components/RecipeForm.svelte +0 -560
- package/apps/recipe-web/src/lib/components/RecipeGrid.svelte +0 -77
- package/apps/recipe-web/src/lib/components/RecipePickerDialog.svelte +0 -185
- package/apps/recipe-web/src/lib/components/Toaster.svelte +0 -111
- package/apps/recipe-web/src/lib/domain/recipe.ts +0 -130
- package/apps/recipe-web/src/lib/domain/validation.ts +0 -189
- package/apps/recipe-web/src/lib/stencil/WebComponent.svelte +0 -145
- package/apps/recipe-web/src/lib/stencil/register.ts +0 -28
- package/apps/recipe-web/src/lib/stores/favorites.svelte.ts +0 -81
- package/apps/recipe-web/src/lib/stores/myRecipes.svelte.ts +0 -98
- package/apps/recipe-web/src/lib/stores/persisted.svelte.ts +0 -73
- package/apps/recipe-web/src/lib/stores/planner.svelte.ts +0 -275
- package/apps/recipe-web/src/lib/stores/theme.svelte.ts +0 -67
- package/apps/recipe-web/src/lib/stores/toast.svelte.ts +0 -66
- package/apps/recipe-web/src/routes/+error.svelte +0 -36
- package/apps/recipe-web/src/routes/+layout.svelte +0 -62
- package/apps/recipe-web/src/routes/+page.svelte +0 -352
- package/apps/recipe-web/src/routes/+page.ts +0 -107
- package/apps/recipe-web/src/routes/favorites/+page.svelte +0 -170
- package/apps/recipe-web/src/routes/my-recipes/+layout.ts +0 -55
- package/apps/recipe-web/src/routes/my-recipes/+page.svelte +0 -180
- package/apps/recipe-web/src/routes/my-recipes/[id]/edit/+page.svelte +0 -104
- package/apps/recipe-web/src/routes/my-recipes/new/+page.svelte +0 -67
- package/apps/recipe-web/src/routes/planner/+page.svelte +0 -422
- package/apps/recipe-web/src/routes/recipes/[id]/+page.svelte +0 -538
- package/apps/recipe-web/src/routes/recipes/[id]/+page.ts +0 -24
- package/apps/recipe-web/static/favicon.svg +0 -8
- package/apps/recipe-web/static/robots.txt +0 -2
- package/apps/recipe-web/svelte.config.js +0 -35
- package/apps/recipe-web/tsconfig.json +0 -14
- package/apps/recipe-web/vite.config.ts +0 -23
- package/packages/recipe-kit-stencil/README.md +0 -185
- package/packages/recipe-kit-stencil/package-lock.json +0 -4772
- package/packages/recipe-kit-stencil/package.json +0 -91
- package/packages/recipe-kit-stencil/src/components/rk-badge/readme.md +0 -38
- package/packages/recipe-kit-stencil/src/components/rk-badge/rk-badge.tsx +0 -31
- package/packages/recipe-kit-stencil/src/components/rk-chip/readme.md +0 -52
- package/packages/recipe-kit-stencil/src/components/rk-chip/rk-chip.tsx +0 -105
- package/packages/recipe-kit-stencil/src/components/rk-empty-state/readme.md +0 -44
- package/packages/recipe-kit-stencil/src/components/rk-empty-state/rk-empty-state.tsx +0 -50
- package/packages/recipe-kit-stencil/src/components/rk-meal-slot/readme.md +0 -58
- package/packages/recipe-kit-stencil/src/components/rk-meal-slot/rk-meal-slot.tsx +0 -182
- package/packages/recipe-kit-stencil/src/components/rk-modal/readme.md +0 -57
- package/packages/recipe-kit-stencil/src/components/rk-modal/rk-modal.tsx +0 -176
- package/packages/recipe-kit-stencil/src/components/rk-rating/readme.md +0 -42
- package/packages/recipe-kit-stencil/src/components/rk-rating/rk-rating.tsx +0 -114
- package/packages/recipe-kit-stencil/src/components/rk-recipe-card/readme.md +0 -66
- package/packages/recipe-kit-stencil/src/components/rk-recipe-card/rk-recipe-card.tsx +0 -179
- package/packages/recipe-kit-stencil/src/components/rk-recipe-card/test/rk-recipe-card.spec.tsx +0 -70
- package/packages/recipe-kit-stencil/src/components/rk-search-bar/readme.md +0 -67
- package/packages/recipe-kit-stencil/src/components/rk-search-bar/rk-search-bar.tsx +0 -152
- package/packages/recipe-kit-stencil/src/components/rk-search-bar/test/rk-search-bar.spec.tsx +0 -93
- package/packages/recipe-kit-stencil/src/components/rk-segmented-control/readme.md +0 -41
- package/packages/recipe-kit-stencil/src/components/rk-segmented-control/rk-segmented-control.tsx +0 -115
- package/packages/recipe-kit-stencil/src/components/rk-tag-input/readme.md +0 -47
- package/packages/recipe-kit-stencil/src/components/rk-tag-input/rk-tag-input.tsx +0 -181
- package/packages/recipe-kit-stencil/src/components/rk-tag-input/test/rk-tag-input.spec.tsx +0 -76
- package/packages/recipe-kit-stencil/src/index.html +0 -259
- package/packages/recipe-kit-stencil/stencil.config.ts +0 -41
- package/packages/recipe-kit-stencil/tsconfig.json +0 -24
- /package/{packages/recipe-kit-stencil/LICENSE → LICENSE} +0 -0
- /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-badge/rk-badge.css +0 -0
- /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-chip/rk-chip.css +0 -0
- /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-empty-state/rk-empty-state.css +0 -0
- /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-meal-slot/rk-meal-slot.css +0 -0
- /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-modal/rk-modal.css +0 -0
- /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-rating/rk-rating.css +0 -0
- /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-recipe-card/rk-recipe-card.css +0 -0
- /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-search-bar/rk-search-bar.css +0 -0
- /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-segmented-control/rk-segmented-control.css +0 -0
- /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-tag-input/rk-tag-input.css +0 -0
- /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
|