@ssingh465/recipe-ui 0.1.1 → 0.2.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/LICENSE +21 -21
- package/dist/components/day-picker.d.ts +11 -0
- package/dist/components/day-picker.js +1 -0
- package/dist/components/empty-state.d.ts +11 -0
- package/dist/components/empty-state.js +1 -0
- package/dist/components/filter-chip.d.ts +11 -0
- package/dist/components/filter-chip.js +1 -0
- package/dist/components/icons.js +1 -0
- package/dist/components/index.js +1 -1
- package/dist/components/planner-day.d.ts +11 -0
- package/dist/components/planner-day.js +1 -0
- package/dist/components/recipe-card.d.ts +11 -0
- package/dist/components/recipe-card.js +1 -0
- package/dist/components/recipe-grid.d.ts +11 -0
- package/dist/components/recipe-grid.js +1 -0
- package/dist/components/skeleton-card.d.ts +11 -0
- package/dist/components/skeleton-card.js +1 -0
- package/dist/components/spike-panel.js +1 -1
- package/dist/types/components/day-picker/day-picker.cmp.test.d.ts +1 -0
- package/dist/types/components/day-picker/day-picker.d.ts +37 -0
- package/dist/types/components/empty-state/empty-state.cmp.test.d.ts +1 -0
- package/dist/types/components/empty-state/empty-state.d.ts +8 -0
- package/dist/types/components/filter-chip/filter-chip.cmp.test.d.ts +1 -0
- package/dist/types/components/filter-chip/filter-chip.d.ts +17 -0
- package/dist/types/components/planner-day/planner-day.cmp.test.d.ts +1 -0
- package/dist/types/components/planner-day/planner-day.d.ts +31 -0
- package/dist/types/components/recipe-card/recipe-card.cmp.test.d.ts +1 -0
- package/dist/types/components/recipe-card/recipe-card.d.ts +22 -0
- package/dist/types/components/recipe-grid/recipe-grid.cmp.test.d.ts +1 -0
- package/dist/types/components/recipe-grid/recipe-grid.d.ts +9 -0
- package/dist/types/components/skeleton-card/skeleton-card.cmp.test.d.ts +1 -0
- package/dist/types/components/skeleton-card/skeleton-card.d.ts +9 -0
- package/dist/types/components.d.ts +414 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/utils/icons.d.ts +7 -0
- package/dist/types/utils/tile-hue.d.ts +1 -0
- package/dist/types/utils/types.d.ts +34 -0
- package/package.json +56 -56
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Shubhanshu Singh
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shubhanshu Singh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Components, JSX } from "../types/components";
|
|
2
|
+
|
|
3
|
+
interface DayPicker extends Components.DayPicker, HTMLElement {}
|
|
4
|
+
export const DayPicker: {
|
|
5
|
+
prototype: DayPicker;
|
|
6
|
+
new (): DayPicker;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Used to define this component and all nested components recursively.
|
|
10
|
+
*/
|
|
11
|
+
export const defineCustomElement: () => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{proxyCustomElement as e,HTMLElement as s,createEvent as i,h as r,Host as t,transformTag as a}from"@stencil/core/internal/client";import{C as n,a as o}from"./icons.js";const c=e(class extends s{constructor(e){super(),!1!==e&&this.__registerHost(),this.__attachShadow(),this.planassign=i(this,"planassign",7),this.pickerclose=i(this,"pickerclose",7)}recipeId;days;label;planassign;pickerclose;open=!1;activeIndex=0;triggerEl;panelEl;dayEls=[];reposition=()=>this.positionPanel();handleOutsideClick=e=>{const s=e.composedPath();this.triggerEl&&this.panelEl&&(s.includes(this.triggerEl)||s.includes(this.panelEl)||this.closePanel(!1))};disconnectedCallback(){window.removeEventListener("resize",this.reposition),window.removeEventListener("scroll",this.reposition,!0),document.removeEventListener("pointerdown",this.handleOutsideClick,!0)}positionPanel(){if(!this.triggerEl||!this.panelEl)return;const e=this.triggerEl.getBoundingClientRect(),s=window.innerHeight-e.bottom<320?e.top-(this.panelEl.offsetHeight||324)-4:e.bottom+4;this.panelEl.style.left=e.left+"px",this.panelEl.style.top=s+"px"}openPanel=()=>{this.open=!0,this.activeIndex=0,requestAnimationFrame((()=>{this.panelEl?.showPopover?.(),this.positionPanel(),this.dayEls[0]?.focus(),window.addEventListener("resize",this.reposition),window.addEventListener("scroll",this.reposition,!0),document.addEventListener("pointerdown",this.handleOutsideClick,!0)}))};closePanel=e=>{this.open&&(this.open=!1,this.panelEl?.hidePopover?.(),window.removeEventListener("resize",this.reposition),window.removeEventListener("scroll",this.reposition,!0),document.removeEventListener("pointerdown",this.handleOutsideClick,!0),this.pickerclose.emit({}),e&&this.triggerEl?.focus())};get safeDays(){return Array.isArray(this.days)?this.days:[]}selectDay(e){e.occupied||(this.planassign.emit({recipeId:this.recipeId,day:e.day}),this.closePanel(!0))}focusIndex(e){const s=this.safeDays.length;0!==s&&(this.activeIndex=(e+s)%s,this.dayEls[this.activeIndex]?.focus())}handlePanelKeydown=e=>{switch(e.key){case"ArrowDown":e.preventDefault(),this.focusIndex(this.activeIndex+1);break;case"ArrowUp":e.preventDefault(),this.focusIndex(this.activeIndex-1);break;case"Home":e.preventDefault(),this.focusIndex(0);break;case"End":e.preventDefault(),this.focusIndex(this.safeDays.length-1);break;case"Tab":e.preventDefault();break;case"Escape":e.preventDefault(),this.closePanel(!0)}};render(){const{open:e,activeIndex:s}=this,i=this.safeDays;return r(t,{key:"c741a8c6d020a2498359c10f4dc03d4e09e1c053"},r("button",{key:"3de198512e8625f819c758fcbeaa3d209e6b24d5",type:"button",part:"trigger",class:"trigger",ref:e=>this.triggerEl=e,"aria-haspopup":"true","aria-expanded":e?"true":"false",onClick:()=>this.open?this.closePanel(!0):this.openPanel()},n(),r("span",{key:"9d67885189a5b44b1a2d776f38e855af2c3116a7"},this.label)),r("div",{key:"3f0bd95d774ee74baaf3bd49d7f1380fbf1adbf9",part:"panel",class:"panel",popover:"manual",role:"group","aria-label":this.label,ref:e=>this.panelEl=e,onKeyDown:this.handlePanelKeydown},i.map(((e,i)=>r("button",{type:"button",part:"day",class:{day:!0,occupied:e.occupied},tabIndex:i===s?0:-1,"aria-disabled":e.occupied?"true":"false",ref:e=>{e&&(this.dayEls[i]=e)},onClick:()=>this.selectDay(e)},r("span",{class:"day-label"},e.label),e.occupied&&r("span",{class:"occupied-tag"},o(),"Already added"))))))}static get style(){return":host{display:inline-block;font-family:var(--rc-font-text, system-ui, sans-serif)}.trigger{display:inline-flex;align-items:center;gap:var(--rc-s-2, 0.5rem);height:36px;padding-inline:var(--rc-s-3, 0.75rem);min-width:44px;min-height:44px;background:transparent;color:var(--rc-text, #2a2420);border:1px solid var(--rc-border-strong, #d4c8b8);border-radius:var(--rc-radius-sm, 6px);font-size:var(--rc-fs-sm, 0.875rem);cursor:pointer}.trigger:focus-visible{outline:none;box-shadow:var(--rc-focus-ring, 0 0 0 2px #fff, 0 0 0 4px #2a2420)}.panel{position:fixed;width:14rem;margin:0;padding:var(--rc-s-2, 0.5rem);border:1px solid var(--rc-border, #e7dfd3);border-radius:var(--rc-radius-sm, 6px);background:var(--rc-surface, #fff);box-shadow:var(--rc-shadow-hover, 0 8px 24px rgba(0, 0, 0, 0.12));color:var(--rc-text, #2a2420);font-family:var(--rc-font-text, system-ui, sans-serif)}.panel:not(:popover-open){display:none}.day{display:flex;align-items:center;justify-content:space-between;width:100%;height:44px;padding-inline:var(--rc-s-2, 0.5rem);border:none;border-radius:var(--rc-radius-sm, 6px);background:transparent;color:var(--rc-text, #2a2420);font-size:var(--rc-fs-body, 1rem);text-align:start;cursor:pointer}.day:hover,.day:focus-visible{background:var(--rc-accent-subtle, #fbede4)}.day:focus-visible{outline:none;box-shadow:var(--rc-focus-ring, 0 0 0 2px #fff, 0 0 0 4px #2a2420)}.day.occupied{color:var(--rc-text-subtle, #8f8377);cursor:default}.day.occupied:hover{background:transparent}.occupied-tag{display:inline-flex;align-items:center;gap:var(--rc-s-1, 0.25rem);font-size:var(--rc-fs-xs, 0.75rem)}"}},[513,"day-picker",{recipeId:[1,"recipe-id"],days:[16],label:[1],open:[32],activeIndex:[32]}]),d=c,l=function(){"undefined"!=typeof customElements&&["day-picker"].forEach((e=>{"day-picker"===e&&(customElements.get(a(e))||customElements.define(a(e),c))}))};export{d as DayPicker,l as defineCustomElement}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Components, JSX } from "../types/components";
|
|
2
|
+
|
|
3
|
+
interface EmptyState extends Components.EmptyState, HTMLElement {}
|
|
4
|
+
export const EmptyState: {
|
|
5
|
+
prototype: EmptyState;
|
|
6
|
+
new (): EmptyState;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Used to define this component and all nested components recursively.
|
|
10
|
+
*/
|
|
11
|
+
export const defineCustomElement: () => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{proxyCustomElement as e,HTMLElement as a,h as t,Host as c,transformTag as s}from"@stencil/core/internal/client";const n=e(class extends a{constructor(e){super(),!1!==e&&this.__registerHost(),this.__attachShadow()}heading;render(){return t(c,{key:"9a79935768889ba37e619d17398ca0b647e1f17c"},t("div",{key:"9c922324505cca8ca67ebb55134ecb42da01c896",part:"container",class:"container"},t("h2",{key:"f0f7595f4d27e381c6d9639a926f3e668a814bd8",part:"heading",class:"heading"},this.heading),t("div",{key:"762f9d21a8bb87d81b64532c1c6347ce18c67536",class:"body"},t("slot",{key:"91ae381cd1eea4b6c23d82e20a374e6ea785834d"})),t("div",{key:"3ac947e41ff7e3bfdaab9753d6e25b6e12cf9818",class:"action"},t("slot",{key:"5c20eb577e4a885a30bc33f260a2a32567c14ac4",name:"action"}))))}static get style(){return":host{display:block}.container{display:flex;flex-direction:column;align-items:center;text-align:center;padding-block:var(--rc-s-6, 2rem);gap:var(--rc-s-3, 0.75rem);font-family:var(--rc-font-text, system-ui, sans-serif);color:var(--rc-text, #2a2420)}.heading{margin:0;font-family:var(--rc-font-display, Georgia, serif);font-size:var(--rc-fs-h3, 1.25rem)}.body{max-width:44ch;color:var(--rc-text-muted, #6b5f55)}"}},[769,"empty-state",{heading:[1]}]),r=n,i=function(){"undefined"!=typeof customElements&&["empty-state"].forEach((e=>{"empty-state"===e&&(customElements.get(s(e))||customElements.define(s(e),n))}))};export{r as EmptyState,i as defineCustomElement}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Components, JSX } from "../types/components";
|
|
2
|
+
|
|
3
|
+
interface FilterChip extends Components.FilterChip, HTMLElement {}
|
|
4
|
+
export const FilterChip: {
|
|
5
|
+
prototype: FilterChip;
|
|
6
|
+
new (): FilterChip;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Used to define this component and all nested components recursively.
|
|
10
|
+
*/
|
|
11
|
+
export const defineCustomElement: () => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{proxyCustomElement as e,HTMLElement as r,createEvent as i,h as t,Host as a,transformTag as s}from"@stencil/core/internal/client";import{X as n}from"./icons.js";const c=e(class extends r{constructor(e){super(),!1!==e&&this.__registerHost(),this.__attachShadow(),this.chiptoggle=i(this,"chiptoggle",7)}label;value;dimension;chiptoggle;handleClick=()=>{this.chiptoggle.emit({dimension:this.dimension,value:this.value})};render(){return t(a,{key:"82857297c365aa1449ef082714d322534073e948"},t("span",{key:"7fa17205fa5a591f47ffa684bd76491be00fa216",part:"chip",class:"chip"},t("span",{key:"76437ede6f06e5ee8b0121d62f1ef6f0881a1fb9",class:"label"},this.label),t("button",{key:"b104ee639f9a5330ce9a0621885f3d17de9d80b0",type:"button",part:"remove",class:"remove","aria-label":`Clear ${this.label} filter`,onClick:this.handleClick},n())))}static get style(){return":host{display:inline-flex;font-family:var(--rc-font-text, system-ui, sans-serif)}.chip{display:inline-flex;align-items:center;gap:var(--rc-s-1, 0.25rem);padding-inline-start:var(--rc-s-3, 0.75rem);border-radius:var(--rc-radius-full, 999px);background:var(--rc-accent-subtle, #fbede4);border:1px solid var(--rc-accent-border, #efcdb6);color:var(--rc-accent, #bc4e1c);font-size:var(--rc-fs-sm, 0.875rem)}.label{white-space:nowrap}.remove{display:flex;align-items:center;justify-content:center;width:44px;height:44px;margin-inline-start:calc(-1 * var(--rc-s-2, 0.5rem));padding:0;border:none;background:transparent;color:inherit;cursor:pointer}.remove:focus-visible{outline:none;box-shadow:var(--rc-focus-ring, 0 0 0 2px #fff, 0 0 0 4px #2a2420);border-radius:var(--rc-radius-full, 999px)}"}},[513,"filter-chip",{label:[1],value:[1],dimension:[1]}]),l=c,o=function(){"undefined"!=typeof customElements&&["filter-chip"].forEach((e=>{"filter-chip"===e&&(customElements.get(s(e))||customElements.define(s(e),c))}))};export{l as FilterChip,o as defineCustomElement}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{h as t}from"@stencil/core/internal/client";const n={viewBox:"0 0 24 24",width:"20",height:"20",fill:"none",stroke:"currentColor","stroke-width":"1.75","stroke-linecap":"round","stroke-linejoin":"round","aria-hidden":"true"};function r(r){return t("svg",{...n,fill:r?"currentColor":"none"},t("path",{d:"M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.29 1.51 4.04 3 5.5l7 7Z"}))}function a(){return t("svg",{...n},t("path",{d:"M18 6 6 18"}),t("path",{d:"m6 6 12 12"}))}function h(){return t("svg",{...n},t("path",{d:"m6 9 6 6 6-6"}))}function e(){return t("svg",{...n},t("path",{d:"m16 3 4 4-4 4"}),t("path",{d:"M20 7H4"}),t("path",{d:"m8 21-4-4 4-4"}),t("path",{d:"M4 17h16"}))}function o(){return t("svg",{...n},t("path",{d:"M3 6h18"}),t("path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"}),t("path",{d:"M10 11v6"}),t("path",{d:"M14 11v6"}))}function d(){return t("svg",{...n},t("path",{d:"M20 6 9 17l-5-5"}))}function i(){return t("svg",{...n},t("path",{d:"M8 2v4"}),t("path",{d:"M16 2v4"}),t("rect",{x:"3",y:"4",width:"18",height:"18",rx:"2"}),t("path",{d:"M3 10h18"}),t("path",{d:"M12 14v6"}),t("path",{d:"M9 17h6"}))}export{e as A,i as C,r as H,o as T,a as X,d as a,h as b}
|
package/dist/components/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{transformTag as e}from"@stencil/core/internal/client";export{getAssetPath,render,setAssetPath,setNonce,setPlatformOptions}from"@stencil/core/internal/client";import{
|
|
1
|
+
import{transformTag as e}from"@stencil/core/internal/client";export{getAssetPath,render,setAssetPath,setNonce,setPlatformOptions}from"@stencil/core/internal/client";import{DayPicker as t}from"./day-picker.js";import{EmptyState as r}from"./empty-state.js";import{FilterChip as o}from"./filter-chip.js";import{PlannerDay as s}from"./planner-day.js";import{RecipeCard as m}from"./recipe-card.js";import{RecipeGrid as i}from"./recipe-grid.js";import{SkeletonCard as n}from"./skeleton-card.js";import{SpikePanel as p}from"./spike-panel.js";const c=c=>{"undefined"!=typeof customElements&&[t,r,o,s,m,i,n,p].forEach((t=>{customElements.get(e(t.is))||customElements.define(e(t.is),t,c)}))};export{c as defineCustomElements}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Components, JSX } from "../types/components";
|
|
2
|
+
|
|
3
|
+
interface PlannerDay extends Components.PlannerDay, HTMLElement {}
|
|
4
|
+
export const PlannerDay: {
|
|
5
|
+
prototype: PlannerDay;
|
|
6
|
+
new (): PlannerDay;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Used to define this component and all nested components recursively.
|
|
10
|
+
*/
|
|
11
|
+
export const defineCustomElement: () => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{proxyCustomElement as e,HTMLElement as r,createEvent as a,h as t,Host as s,transformTag as n}from"@stencil/core/internal/client";import{A as i,T as o,b as c}from"./icons.js";const d=e(class extends r{constructor(e){super(),!1!==e&&this.__registerHost(),this.__attachShadow(),this.entryremove=a(this,"entryremove",7),this.entrymoverequest=a(this,"entrymoverequest",7),this.daytoggle=a(this,"daytoggle",7)}day;dayLabel;entries=[];collapsed=!1;entryremove;entrymoverequest;daytoggle;handleToggle=()=>{this.daytoggle.emit({day:this.day,collapsed:!this.collapsed})};renderEntry(e){return t("li",{part:"entry",class:"entry",key:e.recipeId},e.thumbnail?t("img",{class:"thumb",src:e.thumbnail,alt:"",loading:"lazy",decoding:"async"}):t("span",{class:"thumb placeholder","aria-hidden":"true"}),t("span",{class:"name"},e.name),t("div",{class:"row-actions"},t("button",{type:"button",class:"icon-button","aria-label":"Move "+e.name,onClick:()=>this.entrymoverequest.emit({recipeId:e.recipeId,fromDay:this.day})},i()),t("button",{type:"button",class:"icon-button","aria-label":`Remove ${e.name} from ${this.dayLabel}`,onClick:()=>this.entryremove.emit({recipeId:e.recipeId,day:this.day})},o())))}render(){const{collapsed:e,dayLabel:r}=this,a=Array.isArray(this.entries)?this.entries:[];return t(s,{key:"744e2429e64fa86ac0261a38e3cfee2532fc9011"},t("section",{key:"8aac5476aa01ed06ddc36ae4a9cb40da866eaac1",part:"region",class:"region",role:"region","aria-label":r},t("div",{key:"a337b60503f6c4251e60cb9260aa2fa0ca1f5231",part:"header",class:"header"},t("h3",{key:"4f4528b9df6848c5c2f81487c3ea82786d488b42",class:"day-name"},r),t("span",{key:"8fc2ed5461d1c32defec34809798664aa94d9cc3",class:"count"},a.length),t("button",{key:"02d3cf820cab2796355bec976571ce1a335d96af",type:"button",class:{toggle:!0,"is-collapsed":e},"aria-expanded":e?"false":"true","aria-label":e?"Expand "+r:"Collapse "+r,onClick:this.handleToggle},c())),!e&&(a.length?t("ul",{class:"entries"},a.map((e=>this.renderEntry(e)))):t("div",{class:"empty"},t("slot",{name:"empty"})))))}static get style(){return":host{display:block;font-family:var(--rc-font-text, system-ui, sans-serif)}.region{display:flex;flex-direction:column;height:100%;min-height:20rem;background:var(--rc-surface-sunken, #f6f1e9);border:1px solid var(--rc-border, #e7dfd3);border-radius:var(--rc-radius, 14px);padding:var(--rc-s-4, 1rem);gap:var(--rc-s-3, 0.75rem);color:var(--rc-text, #2a2420)}.header{display:flex;align-items:center;gap:var(--rc-s-2, 0.5rem)}.day-name{margin:0;flex:1;font-family:var(--rc-font-display, Georgia, serif);font-size:var(--rc-fs-h3, 1.25rem)}.count{color:var(--rc-text-muted, #6b5f55);font-size:var(--rc-fs-sm, 0.875rem)}.toggle{display:flex;align-items:center;justify-content:center;width:44px;height:44px;padding:0;border:none;background:transparent;color:var(--rc-text-muted, #6b5f55);cursor:pointer;transition:transform var(--rc-duration, 180ms) var(--rc-ease, ease)}.toggle.is-collapsed{transform:rotate(-90deg)}.toggle:focus-visible{outline:none;box-shadow:var(--rc-focus-ring, 0 0 0 2px #fff, 0 0 0 4px #2a2420);border-radius:var(--rc-radius-sm, 6px)}.entries{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--rc-s-2, 0.5rem)}.entry{display:flex;align-items:center;gap:var(--rc-s-2, 0.5rem);padding:var(--rc-s-2, 0.5rem);background:var(--rc-surface, #fff);border:1px solid var(--rc-border, #e7dfd3);border-radius:var(--rc-radius-sm, 6px)}.thumb{flex:none;width:40px;height:40px;border-radius:var(--rc-radius-sm, 6px);object-fit:cover}.thumb.placeholder{background:var(--rc-border, #e7dfd3)}.name{flex:1;min-width:0;font-size:var(--rc-fs-sm, 0.875rem);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.row-actions{display:flex;gap:var(--rc-s-1, 0.25rem)}.icon-button{display:flex;align-items:center;justify-content:center;width:44px;height:44px;padding:0;border:none;background:transparent;color:var(--rc-text-muted, #6b5f55);cursor:pointer}.icon-button:focus-visible{outline:none;box-shadow:var(--rc-focus-ring, 0 0 0 2px #fff, 0 0 0 4px #2a2420);border-radius:var(--rc-radius-sm, 6px)}.empty{flex:1;display:flex;align-items:center;justify-content:center;color:var(--rc-text-subtle, #8f8377);font-size:var(--rc-fs-sm, 0.875rem)}"}},[769,"planner-day",{day:[1],dayLabel:[1,"day-label"],entries:[16],collapsed:[4]}]),l=d,f=function(){"undefined"!=typeof customElements&&["planner-day"].forEach((e=>{"planner-day"===e&&(customElements.get(n(e))||customElements.define(n(e),d))}))};export{l as PlannerDay,f as defineCustomElement}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Components, JSX } from "../types/components";
|
|
2
|
+
|
|
3
|
+
interface RecipeCard extends Components.RecipeCard, HTMLElement {}
|
|
4
|
+
export const RecipeCard: {
|
|
5
|
+
prototype: RecipeCard;
|
|
6
|
+
new (): RecipeCard;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Used to define this component and all nested components recursively.
|
|
10
|
+
*/
|
|
11
|
+
export const defineCustomElement: () => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{proxyCustomElement as e,HTMLElement as r,createEvent as t,h as a,Host as i,transformTag as o}from"@stencil/core/internal/client";import{H as s}from"./icons.js";const c=[18,34,96,168,262,344],n=e(class extends r{constructor(e){super(),!1!==e&&this.__registerHost(),this.__attachShadow(),this.favoritetoggle=t(this,"favoritetoggle",7),this.recipeselect=t(this,"recipeselect",7)}recipe;favorited=!1;href;favoritetoggle;recipeselect;handleTitleClick=e=>{0!==e.button||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey||(e.preventDefault(),this.recipeselect.emit({recipeId:this.recipe.id}))};handleFavoriteClick=()=>{this.favoritetoggle.emit({recipeId:this.recipe.id})};renderMedia(){const{thumbnail:e,name:r}=this.recipe;if(e)return a("img",{part:"media",class:"media",src:e,alt:"",loading:"lazy",decoding:"async"});const t=function(e){let r=0;for(let t=0;t<e.length;t++)r+=e.charCodeAt(t);return c[r%c.length]}(r);return a("div",{part:"media",class:"media tile",style:{"--tile-h":t+""}},a("span",{class:"tile-name"},r))}render(){const{recipe:e,favorited:r,href:t}=this;return e&&"object"==typeof e?a(i,null,a("article",{part:"card",class:"card"},this.renderMedia(),a("div",{class:"body"},a("a",{part:"title",class:"title",href:t,onClick:this.handleTitleClick},e.name),a("p",{class:"meta"},[e.category,e.area].filter(Boolean).join(" · ")),a("div",{class:"actions-row"},a("button",{type:"button",class:"favorite","aria-pressed":r?"true":"false","aria-label":r?`Remove ${e.name} from favorites`:`Add ${e.name} to favorites`,onClick:this.handleFavoriteClick},s(r)),a("div",{part:"actions",class:"actions"},a("slot",{name:"actions"})))))):a(i,null)}static get style(){return":host{display:block;height:22rem;font-family:var(--rc-font-text, system-ui, sans-serif)}.card{display:flex;flex-direction:column;height:100%;background:var(--rc-surface, #fff);border:1px solid var(--rc-border, #e7dfd3);border-radius:var(--rc-radius, 14px);box-shadow:var(--rc-shadow, 0 1px 2px rgba(0, 0, 0, 0.06));overflow:hidden;transition:box-shadow var(--rc-duration, 180ms) var(--rc-ease, ease), border-color var(--rc-duration, 180ms) var(--rc-ease, ease), transform var(--rc-duration, 180ms) var(--rc-ease, ease)}.card:hover{box-shadow:var(--rc-shadow-hover, 0 2px 8px rgba(0, 0, 0, 0.08));border-color:var(--rc-border-strong, #d4c8b8);transform:translateY(-2px)}.media{height:12rem;flex:none;margin:0}img.media{width:100%;object-fit:cover}.media.tile{display:flex;align-items:center;justify-content:center;padding:var(--rc-s-4, 1rem);text-align:center;background:hsl(var(--tile-h) 42% 88%);color:hsl(var(--tile-h) 45% 28%)}@media (prefers-color-scheme: dark){.media.tile{background:hsl(var(--tile-h) 30% 22%);color:hsl(var(--tile-h) 35% 82%)}}.tile-name{font-family:var(--rc-font-display, Georgia, serif);font-size:var(--rc-fs-h3, 1.25rem);display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}.body{flex:1;min-height:0;display:flex;flex-direction:column;padding:var(--rc-s-4, 1rem);gap:var(--rc-s-2, 0.5rem)}.title{font-family:var(--rc-font-display, Georgia, serif);font-size:var(--rc-fs-h3, 1.25rem);color:var(--rc-text, #2a2420);text-decoration:none;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis}.title:focus-visible{outline:none;box-shadow:var(--rc-focus-ring, 0 0 0 2px #fff, 0 0 0 4px #2a2420);border-radius:var(--rc-radius-sm, 6px)}.meta{margin:0;font-size:var(--rc-fs-sm, 0.875rem);color:var(--rc-text-muted, #6b5f55)}.actions-row{margin-top:auto;display:flex;align-items:center;gap:var(--rc-s-2, 0.5rem)}.favorite{flex:none;display:flex;align-items:center;justify-content:center;width:44px;height:44px;padding:0;border:none;background:transparent;color:var(--rc-text-muted, #6b5f55);cursor:pointer;border-radius:var(--rc-radius-sm, 6px)}.favorite[aria-pressed='true']{color:var(--rc-accent, #bc4e1c)}.favorite:focus-visible{outline:none;box-shadow:var(--rc-focus-ring, 0 0 0 2px #fff, 0 0 0 4px #2a2420)}.actions{display:flex;align-items:center;gap:var(--rc-s-2, 0.5rem)}"}},[769,"recipe-card",{recipe:[16],favorited:[4],href:[1]}]),l=n,d=function(){"undefined"!=typeof customElements&&["recipe-card"].forEach((e=>{"recipe-card"===e&&(customElements.get(o(e))||customElements.define(o(e),n))}))};export{l as RecipeCard,d as defineCustomElement}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Components, JSX } from "../types/components";
|
|
2
|
+
|
|
3
|
+
interface RecipeGrid extends Components.RecipeGrid, HTMLElement {}
|
|
4
|
+
export const RecipeGrid: {
|
|
5
|
+
prototype: RecipeGrid;
|
|
6
|
+
new (): RecipeGrid;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Used to define this component and all nested components recursively.
|
|
10
|
+
*/
|
|
11
|
+
export const defineCustomElement: () => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{proxyCustomElement as e,HTMLElement as t,h as r,Host as s,transformTag as c}from"@stencil/core/internal/client";const d=e(class extends t{constructor(e){super(),!1!==e&&this.__registerHost(),this.__attachShadow()}columns=4;render(){return r(s,{key:"b307b429803a61d0163905e8e0752ffd276f0eca"},r("div",{key:"cf95edd18bc679d3aa8be431a12bf8ad87191202",part:"grid",class:"grid",style:{"--columns":this.columns+""}},r("slot",{key:"1e28d0a6e7fb180925e1cdf88dcf1bb5c2b8fef8"})))}static get style(){return":host{display:block}.grid{display:grid;grid-template-columns:repeat(var(--columns), 1fr);gap:var(--rc-s-4, 1rem)}"}},[769,"recipe-grid",{columns:[2]}]),i=d,n=function(){"undefined"!=typeof customElements&&["recipe-grid"].forEach((e=>{"recipe-grid"===e&&(customElements.get(c(e))||customElements.define(c(e),d))}))};export{i as RecipeGrid,n as defineCustomElement}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Components, JSX } from "../types/components";
|
|
2
|
+
|
|
3
|
+
interface SkeletonCard extends Components.SkeletonCard, HTMLElement {}
|
|
4
|
+
export const SkeletonCard: {
|
|
5
|
+
prototype: SkeletonCard;
|
|
6
|
+
new (): SkeletonCard;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Used to define this component and all nested components recursively.
|
|
10
|
+
*/
|
|
11
|
+
export const defineCustomElement: () => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{proxyCustomElement as e,HTMLElement as r,h as t,Host as n,transformTag as a}from"@stencil/core/internal/client";const i=e(class extends r{constructor(e){super(),!1!==e&&this.__registerHost(),this.__attachShadow()}count=1;render(){const e=Array.from({length:Math.max(1,this.count)});return t(n,{key:"d072f5e79cacb78baafc69a2e52e070fa5934805"},e.map(((e,r)=>t("div",{part:"skeleton",class:"skeleton",key:r,"aria-hidden":"true"},t("div",{class:"media"}),t("div",{class:"body"},t("div",{class:"line title"}),t("div",{class:"line meta"}))))))}static get style(){return":host{display:contents}.skeleton{height:22rem;display:flex;flex-direction:column;background:var(--rc-surface, #fff);border:1px solid var(--rc-border, #e7dfd3);border-radius:var(--rc-radius, 14px);overflow:hidden}.media{height:12rem;flex:none;background:var(--rc-surface-sunken, #f6f1e9);position:relative;overflow:hidden}.body{flex:1;padding:var(--rc-s-4, 1rem);display:flex;flex-direction:column;gap:var(--rc-s-2, 0.5rem)}.line{border-radius:var(--rc-radius-sm, 6px);background:var(--rc-surface-sunken, #f6f1e9);position:relative;overflow:hidden}.line.title{height:1.25rem;width:70%}.line.meta{height:0.875rem;width:45%}.media::after,.line::after{content:'';position:absolute;inset:0;background:linear-gradient(90deg, transparent, var(--rc-border, #e7dfd3) 50%, transparent);opacity:0.3;animation:shimmer 1.4s infinite}@keyframes shimmer{from{transform:translateX(-100%)}to{transform:translateX(100%)}}@media (prefers-reduced-motion: reduce){.media::after,.line::after{animation:none;transform:none;opacity:0.2}}"}},[513,"skeleton-card",{count:[2]}]),s=i,o=function(){"undefined"!=typeof customElements&&["skeleton-card"].forEach((e=>{"skeleton-card"===e&&(customElements.get(a(e))||customElements.define(a(e),i))}))};export{s as SkeletonCard,o as defineCustomElement}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{proxyCustomElement as e,HTMLElement as t,createEvent as s,h as i,Host as n,transformTag as o}from"@stencil/core/internal/client";const a=e(class extends t{constructor(e){super(),!1!==e&&this.__registerHost(),this.__attachShadow(),this.spikeaction=s(this,"spikeaction",7)}note;spikeaction;handleClick=()=>{this.spikeaction.emit(this.note??{from:"spike-panel",message:"no note received"})};render(){return i(n,{key:"
|
|
1
|
+
import{proxyCustomElement as e,HTMLElement as t,createEvent as s,h as i,Host as n,transformTag as o}from"@stencil/core/internal/client";const a=e(class extends t{constructor(e){super(),!1!==e&&this.__registerHost(),this.__attachShadow(),this.spikeaction=s(this,"spikeaction",7)}note;spikeaction;handleClick=()=>{this.spikeaction.emit(this.note??{from:"spike-panel",message:"no note received"})};render(){return i(n,{key:"e2dd4e5cab4edda56f9b23b1da25af36f3f65c84"},i("p",{key:"0ba3c3d40ee49b765eef7af8af438d3b9f5d71fe"},this.note?`From ${this.note.from}: ${this.note.message}`:"Waiting for an object property..."),i("button",{key:"082230f40c2dd4180f3d6552ebd5133fe90531aa",type:"button",onClick:this.handleClick},"Emit spikeaction"),i("div",{key:"f72d1123a8aed1af44d866e5f3c46d77b960ad9a",class:"slot-wrap"},i("slot",{key:"0369ec7f60e0e368eb7630eb00d4d70cc8563204"})))}static get style(){return":host{display:block;border:1px solid #ccc;border-radius:8px;padding:1rem;font-family:system-ui, sans-serif}button{cursor:pointer}.slot-wrap{margin-top:0.75rem}"}},[769,"spike-panel",{note:[16]}]),r=a,c=function(){"undefined"!=typeof customElements&&["spike-panel"].forEach((e=>{"spike-panel"===e&&(customElements.get(o(e))||customElements.define(o(e),a))}))};export{r as SpikePanel,c as defineCustomElement}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { EventEmitter } from '../../stencil-public-runtime';
|
|
2
|
+
import type { DayOfWeek, DayOption } from '../../utils/types';
|
|
3
|
+
/**
|
|
4
|
+
* Assigns a recipe to a day of the plan. Stateless: occupancy is an input
|
|
5
|
+
* (`DayOption.occupied`), never inferred from a store.
|
|
6
|
+
*
|
|
7
|
+
* The panel uses the native Popover API (`popover="manual"`) so it renders
|
|
8
|
+
* in the browser's top layer — the one mechanism that reliably escapes a
|
|
9
|
+
* card's `overflow: hidden` and any transformed ancestor's containing
|
|
10
|
+
* block, which plain `position: fixed` inside a shadow tree does not.
|
|
11
|
+
*/
|
|
12
|
+
export declare class DayPicker {
|
|
13
|
+
recipeId: string;
|
|
14
|
+
days: DayOption[];
|
|
15
|
+
label: string;
|
|
16
|
+
planassign: EventEmitter<{
|
|
17
|
+
recipeId: string;
|
|
18
|
+
day: DayOfWeek;
|
|
19
|
+
}>;
|
|
20
|
+
pickerclose: EventEmitter<Record<string, never>>;
|
|
21
|
+
open: boolean;
|
|
22
|
+
activeIndex: number;
|
|
23
|
+
private triggerEl?;
|
|
24
|
+
private panelEl?;
|
|
25
|
+
private dayEls;
|
|
26
|
+
private reposition;
|
|
27
|
+
private handleOutsideClick;
|
|
28
|
+
disconnectedCallback(): void;
|
|
29
|
+
private positionPanel;
|
|
30
|
+
private openPanel;
|
|
31
|
+
private closePanel;
|
|
32
|
+
private get safeDays();
|
|
33
|
+
private selectDay;
|
|
34
|
+
private focusIndex;
|
|
35
|
+
private handlePanelKeydown;
|
|
36
|
+
render(): any;
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { EventEmitter } from '../../stencil-public-runtime';
|
|
2
|
+
/**
|
|
3
|
+
* A chip only exists while its filter is active, so it has no `active` prop
|
|
4
|
+
* and no separate clear event — removing it and clearing its dimension are
|
|
5
|
+
* the same action.
|
|
6
|
+
*/
|
|
7
|
+
export declare class FilterChip {
|
|
8
|
+
label: string;
|
|
9
|
+
value: string;
|
|
10
|
+
dimension: 'category' | 'area';
|
|
11
|
+
chiptoggle: EventEmitter<{
|
|
12
|
+
dimension: 'category' | 'area';
|
|
13
|
+
value: string;
|
|
14
|
+
}>;
|
|
15
|
+
private handleClick;
|
|
16
|
+
render(): any;
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { EventEmitter } from '../../stencil-public-runtime';
|
|
2
|
+
import type { DayOfWeek, PlannerEntry } from '../../utils/types';
|
|
3
|
+
/**
|
|
4
|
+
* One day's column/section in the planner. Owns its complete accessible
|
|
5
|
+
* name from its own props — `dayLabel` becomes the region's name because
|
|
6
|
+
* ARIA relationships cannot cross the shadow boundary.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately has no `today` prop: the planner is a dateless recurring
|
|
9
|
+
* week, so there is nothing to derive "today" from.
|
|
10
|
+
*/
|
|
11
|
+
export declare class PlannerDay {
|
|
12
|
+
day: DayOfWeek;
|
|
13
|
+
dayLabel: string;
|
|
14
|
+
entries: PlannerEntry[];
|
|
15
|
+
collapsed: boolean;
|
|
16
|
+
entryremove: EventEmitter<{
|
|
17
|
+
recipeId: string;
|
|
18
|
+
day: DayOfWeek;
|
|
19
|
+
}>;
|
|
20
|
+
entrymoverequest: EventEmitter<{
|
|
21
|
+
recipeId: string;
|
|
22
|
+
fromDay: DayOfWeek;
|
|
23
|
+
}>;
|
|
24
|
+
daytoggle: EventEmitter<{
|
|
25
|
+
day: DayOfWeek;
|
|
26
|
+
collapsed: boolean;
|
|
27
|
+
}>;
|
|
28
|
+
private handleToggle;
|
|
29
|
+
private renderEntry;
|
|
30
|
+
render(): any;
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { EventEmitter } from '../../stencil-public-runtime';
|
|
2
|
+
import type { RecipeSummary } from '../../utils/types';
|
|
3
|
+
/**
|
|
4
|
+
* The app's primary discovery/favorites/planner unit. Fixed 22rem height
|
|
5
|
+
* regardless of content — recipe-card:not(:defined) must reserve the same
|
|
6
|
+
* value so an unregistered element doesn't cause a layout jump.
|
|
7
|
+
*/
|
|
8
|
+
export declare class RecipeCard {
|
|
9
|
+
recipe: RecipeSummary;
|
|
10
|
+
favorited: boolean;
|
|
11
|
+
href: string;
|
|
12
|
+
favoritetoggle: EventEmitter<{
|
|
13
|
+
recipeId: string;
|
|
14
|
+
}>;
|
|
15
|
+
recipeselect: EventEmitter<{
|
|
16
|
+
recipeId: string;
|
|
17
|
+
}>;
|
|
18
|
+
private handleTitleClick;
|
|
19
|
+
private handleFavoriteClick;
|
|
20
|
+
private renderMedia;
|
|
21
|
+
render(): any;
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loading placeholder that matches `recipe-card`'s geometry exactly, so the
|
|
3
|
+
* reserved space for an undefined custom element holds and the grid does
|
|
4
|
+
* not shift when real cards arrive.
|
|
5
|
+
*/
|
|
6
|
+
export declare class SkeletonCard {
|
|
7
|
+
count: number;
|
|
8
|
+
render(): any;
|
|
9
|
+
}
|
|
@@ -5,9 +5,95 @@
|
|
|
5
5
|
* It contains typing information for all components that exist in this project.
|
|
6
6
|
*/
|
|
7
7
|
import { HTMLStencilElement, JSXBase } from "./stencil-public-runtime";
|
|
8
|
+
import { DayOfWeek, DayOption, PlannerEntry, RecipeSummary } from "./utils/types";
|
|
8
9
|
import { SpikeNote } from "./components/spike-panel/spike-panel";
|
|
10
|
+
export { DayOfWeek, DayOption, PlannerEntry, RecipeSummary } from "./utils/types";
|
|
9
11
|
export { SpikeNote } from "./components/spike-panel/spike-panel";
|
|
10
12
|
export namespace Components {
|
|
13
|
+
/**
|
|
14
|
+
* Assigns a recipe to a day of the plan. Stateless: occupancy is an input
|
|
15
|
+
* (`DayOption.occupied`), never inferred from a store.
|
|
16
|
+
* The panel uses the native Popover API (`popover="manual"`) so it renders
|
|
17
|
+
* in the browser's top layer — the one mechanism that reliably escapes a
|
|
18
|
+
* card's `overflow: hidden` and any transformed ancestor's containing
|
|
19
|
+
* block, which plain `position: fixed` inside a shadow tree does not.
|
|
20
|
+
*/
|
|
21
|
+
interface DayPicker {
|
|
22
|
+
"days": DayOption[];
|
|
23
|
+
"label": string;
|
|
24
|
+
"recipeId": string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A generic shell for empty results — the app slots in the wording and,
|
|
28
|
+
* optionally, a call-to-action button.
|
|
29
|
+
*/
|
|
30
|
+
interface EmptyState {
|
|
31
|
+
"heading": string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A chip only exists while its filter is active, so it has no `active` prop
|
|
35
|
+
* and no separate clear event — removing it and clearing its dimension are
|
|
36
|
+
* the same action.
|
|
37
|
+
*/
|
|
38
|
+
interface FilterChip {
|
|
39
|
+
"dimension": 'category' | 'area';
|
|
40
|
+
"label": string;
|
|
41
|
+
"value": string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* One day's column/section in the planner. Owns its complete accessible
|
|
45
|
+
* name from its own props — `dayLabel` becomes the region's name because
|
|
46
|
+
* ARIA relationships cannot cross the shadow boundary.
|
|
47
|
+
* Deliberately has no `today` prop: the planner is a dateless recurring
|
|
48
|
+
* week, so there is nothing to derive "today" from.
|
|
49
|
+
*/
|
|
50
|
+
interface PlannerDay {
|
|
51
|
+
/**
|
|
52
|
+
* @default false
|
|
53
|
+
*/
|
|
54
|
+
"collapsed": boolean;
|
|
55
|
+
"day": DayOfWeek;
|
|
56
|
+
"dayLabel": string;
|
|
57
|
+
/**
|
|
58
|
+
* @default []
|
|
59
|
+
*/
|
|
60
|
+
"entries": PlannerEntry[];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The app's primary discovery/favorites/planner unit. Fixed 22rem height
|
|
64
|
+
* regardless of content — recipe-card:not(:defined) must reserve the same
|
|
65
|
+
* value so an unregistered element doesn't cause a layout jump.
|
|
66
|
+
*/
|
|
67
|
+
interface RecipeCard {
|
|
68
|
+
/**
|
|
69
|
+
* @default false
|
|
70
|
+
*/
|
|
71
|
+
"favorited": boolean;
|
|
72
|
+
"href": string;
|
|
73
|
+
"recipe": RecipeSummary;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* A responsive card grid with an explicit column count — never `auto-fill`,
|
|
77
|
+
* so the app decides the breakpoint behaviour instead of the browser
|
|
78
|
+
* guessing it.
|
|
79
|
+
*/
|
|
80
|
+
interface RecipeGrid {
|
|
81
|
+
/**
|
|
82
|
+
* @default 4
|
|
83
|
+
*/
|
|
84
|
+
"columns": 1 | 2 | 3 | 4;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Loading placeholder that matches `recipe-card`'s geometry exactly, so the
|
|
88
|
+
* reserved space for an undefined custom element holds and the grid does
|
|
89
|
+
* not shift when real cards arrive.
|
|
90
|
+
*/
|
|
91
|
+
interface SkeletonCard {
|
|
92
|
+
/**
|
|
93
|
+
* @default 1
|
|
94
|
+
*/
|
|
95
|
+
"count": number;
|
|
96
|
+
}
|
|
11
97
|
/**
|
|
12
98
|
* Integration spike only. Proves the Stencil <-> SvelteKit seam (object
|
|
13
99
|
* property, custom event, slot) end to end. Not part of the real component
|
|
@@ -20,11 +106,156 @@ export namespace Components {
|
|
|
20
106
|
"note"?: SpikeNote;
|
|
21
107
|
}
|
|
22
108
|
}
|
|
109
|
+
export interface DayPickerCustomEvent<T> extends CustomEvent<T> {
|
|
110
|
+
detail: T;
|
|
111
|
+
target: HTMLDayPickerElement;
|
|
112
|
+
}
|
|
113
|
+
export interface FilterChipCustomEvent<T> extends CustomEvent<T> {
|
|
114
|
+
detail: T;
|
|
115
|
+
target: HTMLFilterChipElement;
|
|
116
|
+
}
|
|
117
|
+
export interface PlannerDayCustomEvent<T> extends CustomEvent<T> {
|
|
118
|
+
detail: T;
|
|
119
|
+
target: HTMLPlannerDayElement;
|
|
120
|
+
}
|
|
121
|
+
export interface RecipeCardCustomEvent<T> extends CustomEvent<T> {
|
|
122
|
+
detail: T;
|
|
123
|
+
target: HTMLRecipeCardElement;
|
|
124
|
+
}
|
|
23
125
|
export interface SpikePanelCustomEvent<T> extends CustomEvent<T> {
|
|
24
126
|
detail: T;
|
|
25
127
|
target: HTMLSpikePanelElement;
|
|
26
128
|
}
|
|
27
129
|
declare global {
|
|
130
|
+
interface HTMLDayPickerElementEventMap {
|
|
131
|
+
"planassign": { recipeId: string; day: DayOfWeek };
|
|
132
|
+
"pickerclose": Record<string, never>;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Assigns a recipe to a day of the plan. Stateless: occupancy is an input
|
|
136
|
+
* (`DayOption.occupied`), never inferred from a store.
|
|
137
|
+
* The panel uses the native Popover API (`popover="manual"`) so it renders
|
|
138
|
+
* in the browser's top layer — the one mechanism that reliably escapes a
|
|
139
|
+
* card's `overflow: hidden` and any transformed ancestor's containing
|
|
140
|
+
* block, which plain `position: fixed` inside a shadow tree does not.
|
|
141
|
+
*/
|
|
142
|
+
interface HTMLDayPickerElement extends Components.DayPicker, HTMLStencilElement {
|
|
143
|
+
addEventListener<K extends keyof HTMLDayPickerElementEventMap>(type: K, listener: (this: HTMLDayPickerElement, ev: DayPickerCustomEvent<HTMLDayPickerElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
|
|
144
|
+
addEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
|
|
145
|
+
addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
|
|
146
|
+
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
|
147
|
+
removeEventListener<K extends keyof HTMLDayPickerElementEventMap>(type: K, listener: (this: HTMLDayPickerElement, ev: DayPickerCustomEvent<HTMLDayPickerElementEventMap[K]>) => any, options?: boolean | EventListenerOptions): void;
|
|
148
|
+
removeEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
|
|
149
|
+
removeEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
|
|
150
|
+
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
|
|
151
|
+
}
|
|
152
|
+
var HTMLDayPickerElement: {
|
|
153
|
+
prototype: HTMLDayPickerElement;
|
|
154
|
+
new (): HTMLDayPickerElement;
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* A generic shell for empty results — the app slots in the wording and,
|
|
158
|
+
* optionally, a call-to-action button.
|
|
159
|
+
*/
|
|
160
|
+
interface HTMLEmptyStateElement extends Components.EmptyState, HTMLStencilElement {
|
|
161
|
+
}
|
|
162
|
+
var HTMLEmptyStateElement: {
|
|
163
|
+
prototype: HTMLEmptyStateElement;
|
|
164
|
+
new (): HTMLEmptyStateElement;
|
|
165
|
+
};
|
|
166
|
+
interface HTMLFilterChipElementEventMap {
|
|
167
|
+
"chiptoggle": { dimension: 'category' | 'area'; value: string };
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* A chip only exists while its filter is active, so it has no `active` prop
|
|
171
|
+
* and no separate clear event — removing it and clearing its dimension are
|
|
172
|
+
* the same action.
|
|
173
|
+
*/
|
|
174
|
+
interface HTMLFilterChipElement extends Components.FilterChip, HTMLStencilElement {
|
|
175
|
+
addEventListener<K extends keyof HTMLFilterChipElementEventMap>(type: K, listener: (this: HTMLFilterChipElement, ev: FilterChipCustomEvent<HTMLFilterChipElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
|
|
176
|
+
addEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
|
|
177
|
+
addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
|
|
178
|
+
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
|
179
|
+
removeEventListener<K extends keyof HTMLFilterChipElementEventMap>(type: K, listener: (this: HTMLFilterChipElement, ev: FilterChipCustomEvent<HTMLFilterChipElementEventMap[K]>) => any, options?: boolean | EventListenerOptions): void;
|
|
180
|
+
removeEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
|
|
181
|
+
removeEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
|
|
182
|
+
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
|
|
183
|
+
}
|
|
184
|
+
var HTMLFilterChipElement: {
|
|
185
|
+
prototype: HTMLFilterChipElement;
|
|
186
|
+
new (): HTMLFilterChipElement;
|
|
187
|
+
};
|
|
188
|
+
interface HTMLPlannerDayElementEventMap {
|
|
189
|
+
"entryremove": { recipeId: string; day: DayOfWeek };
|
|
190
|
+
"entrymoverequest": { recipeId: string; fromDay: DayOfWeek };
|
|
191
|
+
"daytoggle": { day: DayOfWeek; collapsed: boolean };
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* One day's column/section in the planner. Owns its complete accessible
|
|
195
|
+
* name from its own props — `dayLabel` becomes the region's name because
|
|
196
|
+
* ARIA relationships cannot cross the shadow boundary.
|
|
197
|
+
* Deliberately has no `today` prop: the planner is a dateless recurring
|
|
198
|
+
* week, so there is nothing to derive "today" from.
|
|
199
|
+
*/
|
|
200
|
+
interface HTMLPlannerDayElement extends Components.PlannerDay, HTMLStencilElement {
|
|
201
|
+
addEventListener<K extends keyof HTMLPlannerDayElementEventMap>(type: K, listener: (this: HTMLPlannerDayElement, ev: PlannerDayCustomEvent<HTMLPlannerDayElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
|
|
202
|
+
addEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
|
|
203
|
+
addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
|
|
204
|
+
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
|
205
|
+
removeEventListener<K extends keyof HTMLPlannerDayElementEventMap>(type: K, listener: (this: HTMLPlannerDayElement, ev: PlannerDayCustomEvent<HTMLPlannerDayElementEventMap[K]>) => any, options?: boolean | EventListenerOptions): void;
|
|
206
|
+
removeEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
|
|
207
|
+
removeEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
|
|
208
|
+
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
|
|
209
|
+
}
|
|
210
|
+
var HTMLPlannerDayElement: {
|
|
211
|
+
prototype: HTMLPlannerDayElement;
|
|
212
|
+
new (): HTMLPlannerDayElement;
|
|
213
|
+
};
|
|
214
|
+
interface HTMLRecipeCardElementEventMap {
|
|
215
|
+
"favoritetoggle": { recipeId: string };
|
|
216
|
+
"recipeselect": { recipeId: string };
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* The app's primary discovery/favorites/planner unit. Fixed 22rem height
|
|
220
|
+
* regardless of content — recipe-card:not(:defined) must reserve the same
|
|
221
|
+
* value so an unregistered element doesn't cause a layout jump.
|
|
222
|
+
*/
|
|
223
|
+
interface HTMLRecipeCardElement extends Components.RecipeCard, HTMLStencilElement {
|
|
224
|
+
addEventListener<K extends keyof HTMLRecipeCardElementEventMap>(type: K, listener: (this: HTMLRecipeCardElement, ev: RecipeCardCustomEvent<HTMLRecipeCardElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
|
|
225
|
+
addEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
|
|
226
|
+
addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
|
|
227
|
+
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
|
228
|
+
removeEventListener<K extends keyof HTMLRecipeCardElementEventMap>(type: K, listener: (this: HTMLRecipeCardElement, ev: RecipeCardCustomEvent<HTMLRecipeCardElementEventMap[K]>) => any, options?: boolean | EventListenerOptions): void;
|
|
229
|
+
removeEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
|
|
230
|
+
removeEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
|
|
231
|
+
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
|
|
232
|
+
}
|
|
233
|
+
var HTMLRecipeCardElement: {
|
|
234
|
+
prototype: HTMLRecipeCardElement;
|
|
235
|
+
new (): HTMLRecipeCardElement;
|
|
236
|
+
};
|
|
237
|
+
/**
|
|
238
|
+
* A responsive card grid with an explicit column count — never `auto-fill`,
|
|
239
|
+
* so the app decides the breakpoint behaviour instead of the browser
|
|
240
|
+
* guessing it.
|
|
241
|
+
*/
|
|
242
|
+
interface HTMLRecipeGridElement extends Components.RecipeGrid, HTMLStencilElement {
|
|
243
|
+
}
|
|
244
|
+
var HTMLRecipeGridElement: {
|
|
245
|
+
prototype: HTMLRecipeGridElement;
|
|
246
|
+
new (): HTMLRecipeGridElement;
|
|
247
|
+
};
|
|
248
|
+
/**
|
|
249
|
+
* Loading placeholder that matches `recipe-card`'s geometry exactly, so the
|
|
250
|
+
* reserved space for an undefined custom element holds and the grid does
|
|
251
|
+
* not shift when real cards arrive.
|
|
252
|
+
*/
|
|
253
|
+
interface HTMLSkeletonCardElement extends Components.SkeletonCard, HTMLStencilElement {
|
|
254
|
+
}
|
|
255
|
+
var HTMLSkeletonCardElement: {
|
|
256
|
+
prototype: HTMLSkeletonCardElement;
|
|
257
|
+
new (): HTMLSkeletonCardElement;
|
|
258
|
+
};
|
|
28
259
|
interface HTMLSpikePanelElementEventMap {
|
|
29
260
|
"spikeaction": SpikeNote;
|
|
30
261
|
}
|
|
@@ -48,10 +279,111 @@ declare global {
|
|
|
48
279
|
new (): HTMLSpikePanelElement;
|
|
49
280
|
};
|
|
50
281
|
interface HTMLElementTagNameMap {
|
|
282
|
+
"day-picker": HTMLDayPickerElement;
|
|
283
|
+
"empty-state": HTMLEmptyStateElement;
|
|
284
|
+
"filter-chip": HTMLFilterChipElement;
|
|
285
|
+
"planner-day": HTMLPlannerDayElement;
|
|
286
|
+
"recipe-card": HTMLRecipeCardElement;
|
|
287
|
+
"recipe-grid": HTMLRecipeGridElement;
|
|
288
|
+
"skeleton-card": HTMLSkeletonCardElement;
|
|
51
289
|
"spike-panel": HTMLSpikePanelElement;
|
|
52
290
|
}
|
|
53
291
|
}
|
|
54
292
|
declare namespace LocalJSX {
|
|
293
|
+
type OneOf<K extends string, PropT, AttrT = PropT> = { [P in K]: PropT } & { [P in `attr:${K}`]?: never } | { [P in `attr:${K}`]: AttrT } & { [P in K]?: never };
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Assigns a recipe to a day of the plan. Stateless: occupancy is an input
|
|
297
|
+
* (`DayOption.occupied`), never inferred from a store.
|
|
298
|
+
* The panel uses the native Popover API (`popover="manual"`) so it renders
|
|
299
|
+
* in the browser's top layer — the one mechanism that reliably escapes a
|
|
300
|
+
* card's `overflow: hidden` and any transformed ancestor's containing
|
|
301
|
+
* block, which plain `position: fixed` inside a shadow tree does not.
|
|
302
|
+
*/
|
|
303
|
+
interface DayPicker {
|
|
304
|
+
"days": DayOption[];
|
|
305
|
+
"label": string;
|
|
306
|
+
"onPickerclose"?: (event: DayPickerCustomEvent<Record<string, never>>) => void;
|
|
307
|
+
"onPlanassign"?: (event: DayPickerCustomEvent<{ recipeId: string; day: DayOfWeek }>) => void;
|
|
308
|
+
"recipeId": string;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* A generic shell for empty results — the app slots in the wording and,
|
|
312
|
+
* optionally, a call-to-action button.
|
|
313
|
+
*/
|
|
314
|
+
interface EmptyState {
|
|
315
|
+
"heading": string;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* A chip only exists while its filter is active, so it has no `active` prop
|
|
319
|
+
* and no separate clear event — removing it and clearing its dimension are
|
|
320
|
+
* the same action.
|
|
321
|
+
*/
|
|
322
|
+
interface FilterChip {
|
|
323
|
+
"dimension": 'category' | 'area';
|
|
324
|
+
"label": string;
|
|
325
|
+
"onChiptoggle"?: (event: FilterChipCustomEvent<{ dimension: 'category' | 'area'; value: string }>) => void;
|
|
326
|
+
"value": string;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* One day's column/section in the planner. Owns its complete accessible
|
|
330
|
+
* name from its own props — `dayLabel` becomes the region's name because
|
|
331
|
+
* ARIA relationships cannot cross the shadow boundary.
|
|
332
|
+
* Deliberately has no `today` prop: the planner is a dateless recurring
|
|
333
|
+
* week, so there is nothing to derive "today" from.
|
|
334
|
+
*/
|
|
335
|
+
interface PlannerDay {
|
|
336
|
+
/**
|
|
337
|
+
* @default false
|
|
338
|
+
*/
|
|
339
|
+
"collapsed"?: boolean;
|
|
340
|
+
"day": DayOfWeek;
|
|
341
|
+
"dayLabel": string;
|
|
342
|
+
/**
|
|
343
|
+
* @default []
|
|
344
|
+
*/
|
|
345
|
+
"entries"?: PlannerEntry[];
|
|
346
|
+
"onDaytoggle"?: (event: PlannerDayCustomEvent<{ day: DayOfWeek; collapsed: boolean }>) => void;
|
|
347
|
+
"onEntrymoverequest"?: (event: PlannerDayCustomEvent<{ recipeId: string; fromDay: DayOfWeek }>) => void;
|
|
348
|
+
"onEntryremove"?: (event: PlannerDayCustomEvent<{ recipeId: string; day: DayOfWeek }>) => void;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* The app's primary discovery/favorites/planner unit. Fixed 22rem height
|
|
352
|
+
* regardless of content — recipe-card:not(:defined) must reserve the same
|
|
353
|
+
* value so an unregistered element doesn't cause a layout jump.
|
|
354
|
+
*/
|
|
355
|
+
interface RecipeCard {
|
|
356
|
+
/**
|
|
357
|
+
* @default false
|
|
358
|
+
*/
|
|
359
|
+
"favorited"?: boolean;
|
|
360
|
+
"href": string;
|
|
361
|
+
"onFavoritetoggle"?: (event: RecipeCardCustomEvent<{ recipeId: string }>) => void;
|
|
362
|
+
"onRecipeselect"?: (event: RecipeCardCustomEvent<{ recipeId: string }>) => void;
|
|
363
|
+
"recipe": RecipeSummary;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* A responsive card grid with an explicit column count — never `auto-fill`,
|
|
367
|
+
* so the app decides the breakpoint behaviour instead of the browser
|
|
368
|
+
* guessing it.
|
|
369
|
+
*/
|
|
370
|
+
interface RecipeGrid {
|
|
371
|
+
/**
|
|
372
|
+
* @default 4
|
|
373
|
+
*/
|
|
374
|
+
"columns"?: 1 | 2 | 3 | 4;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Loading placeholder that matches `recipe-card`'s geometry exactly, so the
|
|
378
|
+
* reserved space for an undefined custom element holds and the grid does
|
|
379
|
+
* not shift when real cards arrive.
|
|
380
|
+
*/
|
|
381
|
+
interface SkeletonCard {
|
|
382
|
+
/**
|
|
383
|
+
* @default 1
|
|
384
|
+
*/
|
|
385
|
+
"count"?: number;
|
|
386
|
+
}
|
|
55
387
|
/**
|
|
56
388
|
* Integration spike only. Proves the Stencil <-> SvelteKit seam (object
|
|
57
389
|
* property, custom event, slot) end to end. Not part of the real component
|
|
@@ -67,7 +399,43 @@ declare namespace LocalJSX {
|
|
|
67
399
|
*/
|
|
68
400
|
"onSpikeaction"?: (event: SpikePanelCustomEvent<SpikeNote>) => void;
|
|
69
401
|
}
|
|
402
|
+
|
|
403
|
+
interface DayPickerAttributes {
|
|
404
|
+
"recipeId": string;
|
|
405
|
+
"label": string;
|
|
406
|
+
}
|
|
407
|
+
interface EmptyStateAttributes {
|
|
408
|
+
"heading": string;
|
|
409
|
+
}
|
|
410
|
+
interface FilterChipAttributes {
|
|
411
|
+
"label": string;
|
|
412
|
+
"value": string;
|
|
413
|
+
"dimension": 'category' | 'area';
|
|
414
|
+
}
|
|
415
|
+
interface PlannerDayAttributes {
|
|
416
|
+
"day": DayOfWeek;
|
|
417
|
+
"dayLabel": string;
|
|
418
|
+
"collapsed": boolean;
|
|
419
|
+
}
|
|
420
|
+
interface RecipeCardAttributes {
|
|
421
|
+
"favorited": boolean;
|
|
422
|
+
"href": string;
|
|
423
|
+
}
|
|
424
|
+
interface RecipeGridAttributes {
|
|
425
|
+
"columns": 1 | 2 | 3 | 4;
|
|
426
|
+
}
|
|
427
|
+
interface SkeletonCardAttributes {
|
|
428
|
+
"count": number;
|
|
429
|
+
}
|
|
430
|
+
|
|
70
431
|
interface IntrinsicElements {
|
|
432
|
+
"day-picker": Omit<DayPicker, keyof DayPickerAttributes> & { [K in keyof DayPicker & keyof DayPickerAttributes]?: DayPicker[K] } & { [K in keyof DayPicker & keyof DayPickerAttributes as `attr:${K}`]?: DayPickerAttributes[K] } & { [K in keyof DayPicker & keyof DayPickerAttributes as `prop:${K}`]?: DayPicker[K] } & OneOf<"recipeId", DayPicker["recipeId"], DayPickerAttributes["recipeId"]> & OneOf<"label", DayPicker["label"], DayPickerAttributes["label"]>;
|
|
433
|
+
"empty-state": Omit<EmptyState, keyof EmptyStateAttributes> & { [K in keyof EmptyState & keyof EmptyStateAttributes]?: EmptyState[K] } & { [K in keyof EmptyState & keyof EmptyStateAttributes as `attr:${K}`]?: EmptyStateAttributes[K] } & { [K in keyof EmptyState & keyof EmptyStateAttributes as `prop:${K}`]?: EmptyState[K] } & OneOf<"heading", EmptyState["heading"], EmptyStateAttributes["heading"]>;
|
|
434
|
+
"filter-chip": Omit<FilterChip, keyof FilterChipAttributes> & { [K in keyof FilterChip & keyof FilterChipAttributes]?: FilterChip[K] } & { [K in keyof FilterChip & keyof FilterChipAttributes as `attr:${K}`]?: FilterChipAttributes[K] } & { [K in keyof FilterChip & keyof FilterChipAttributes as `prop:${K}`]?: FilterChip[K] } & OneOf<"label", FilterChip["label"], FilterChipAttributes["label"]> & OneOf<"value", FilterChip["value"], FilterChipAttributes["value"]> & OneOf<"dimension", FilterChip["dimension"], FilterChipAttributes["dimension"]>;
|
|
435
|
+
"planner-day": Omit<PlannerDay, keyof PlannerDayAttributes> & { [K in keyof PlannerDay & keyof PlannerDayAttributes]?: PlannerDay[K] } & { [K in keyof PlannerDay & keyof PlannerDayAttributes as `attr:${K}`]?: PlannerDayAttributes[K] } & { [K in keyof PlannerDay & keyof PlannerDayAttributes as `prop:${K}`]?: PlannerDay[K] } & OneOf<"day", PlannerDay["day"], PlannerDayAttributes["day"]> & OneOf<"dayLabel", PlannerDay["dayLabel"], PlannerDayAttributes["dayLabel"]>;
|
|
436
|
+
"recipe-card": Omit<RecipeCard, keyof RecipeCardAttributes> & { [K in keyof RecipeCard & keyof RecipeCardAttributes]?: RecipeCard[K] } & { [K in keyof RecipeCard & keyof RecipeCardAttributes as `attr:${K}`]?: RecipeCardAttributes[K] } & { [K in keyof RecipeCard & keyof RecipeCardAttributes as `prop:${K}`]?: RecipeCard[K] } & OneOf<"href", RecipeCard["href"], RecipeCardAttributes["href"]>;
|
|
437
|
+
"recipe-grid": Omit<RecipeGrid, keyof RecipeGridAttributes> & { [K in keyof RecipeGrid & keyof RecipeGridAttributes]?: RecipeGrid[K] } & { [K in keyof RecipeGrid & keyof RecipeGridAttributes as `attr:${K}`]?: RecipeGridAttributes[K] } & { [K in keyof RecipeGrid & keyof RecipeGridAttributes as `prop:${K}`]?: RecipeGrid[K] };
|
|
438
|
+
"skeleton-card": Omit<SkeletonCard, keyof SkeletonCardAttributes> & { [K in keyof SkeletonCard & keyof SkeletonCardAttributes]?: SkeletonCard[K] } & { [K in keyof SkeletonCard & keyof SkeletonCardAttributes as `attr:${K}`]?: SkeletonCardAttributes[K] } & { [K in keyof SkeletonCard & keyof SkeletonCardAttributes as `prop:${K}`]?: SkeletonCard[K] };
|
|
71
439
|
"spike-panel": SpikePanel;
|
|
72
440
|
}
|
|
73
441
|
}
|
|
@@ -75,6 +443,52 @@ export { LocalJSX as JSX };
|
|
|
75
443
|
declare module "@stencil/core" {
|
|
76
444
|
export namespace JSX {
|
|
77
445
|
interface IntrinsicElements {
|
|
446
|
+
/**
|
|
447
|
+
* Assigns a recipe to a day of the plan. Stateless: occupancy is an input
|
|
448
|
+
* (`DayOption.occupied`), never inferred from a store.
|
|
449
|
+
* The panel uses the native Popover API (`popover="manual"`) so it renders
|
|
450
|
+
* in the browser's top layer — the one mechanism that reliably escapes a
|
|
451
|
+
* card's `overflow: hidden` and any transformed ancestor's containing
|
|
452
|
+
* block, which plain `position: fixed` inside a shadow tree does not.
|
|
453
|
+
*/
|
|
454
|
+
"day-picker": LocalJSX.IntrinsicElements["day-picker"] & JSXBase.HTMLAttributes<HTMLDayPickerElement>;
|
|
455
|
+
/**
|
|
456
|
+
* A generic shell for empty results — the app slots in the wording and,
|
|
457
|
+
* optionally, a call-to-action button.
|
|
458
|
+
*/
|
|
459
|
+
"empty-state": LocalJSX.IntrinsicElements["empty-state"] & JSXBase.HTMLAttributes<HTMLEmptyStateElement>;
|
|
460
|
+
/**
|
|
461
|
+
* A chip only exists while its filter is active, so it has no `active` prop
|
|
462
|
+
* and no separate clear event — removing it and clearing its dimension are
|
|
463
|
+
* the same action.
|
|
464
|
+
*/
|
|
465
|
+
"filter-chip": LocalJSX.IntrinsicElements["filter-chip"] & JSXBase.HTMLAttributes<HTMLFilterChipElement>;
|
|
466
|
+
/**
|
|
467
|
+
* One day's column/section in the planner. Owns its complete accessible
|
|
468
|
+
* name from its own props — `dayLabel` becomes the region's name because
|
|
469
|
+
* ARIA relationships cannot cross the shadow boundary.
|
|
470
|
+
* Deliberately has no `today` prop: the planner is a dateless recurring
|
|
471
|
+
* week, so there is nothing to derive "today" from.
|
|
472
|
+
*/
|
|
473
|
+
"planner-day": LocalJSX.IntrinsicElements["planner-day"] & JSXBase.HTMLAttributes<HTMLPlannerDayElement>;
|
|
474
|
+
/**
|
|
475
|
+
* The app's primary discovery/favorites/planner unit. Fixed 22rem height
|
|
476
|
+
* regardless of content — recipe-card:not(:defined) must reserve the same
|
|
477
|
+
* value so an unregistered element doesn't cause a layout jump.
|
|
478
|
+
*/
|
|
479
|
+
"recipe-card": LocalJSX.IntrinsicElements["recipe-card"] & JSXBase.HTMLAttributes<HTMLRecipeCardElement>;
|
|
480
|
+
/**
|
|
481
|
+
* A responsive card grid with an explicit column count — never `auto-fill`,
|
|
482
|
+
* so the app decides the breakpoint behaviour instead of the browser
|
|
483
|
+
* guessing it.
|
|
484
|
+
*/
|
|
485
|
+
"recipe-grid": LocalJSX.IntrinsicElements["recipe-grid"] & JSXBase.HTMLAttributes<HTMLRecipeGridElement>;
|
|
486
|
+
/**
|
|
487
|
+
* Loading placeholder that matches `recipe-card`'s geometry exactly, so the
|
|
488
|
+
* reserved space for an undefined custom element holds and the grid does
|
|
489
|
+
* not shift when real cards arrive.
|
|
490
|
+
*/
|
|
491
|
+
"skeleton-card": LocalJSX.IntrinsicElements["skeleton-card"] & JSXBase.HTMLAttributes<HTMLSkeletonCardElement>;
|
|
78
492
|
/**
|
|
79
493
|
* Integration spike only. Proves the Stencil <-> SvelteKit seam (object
|
|
80
494
|
* property, custom event, slot) end to end. Not part of the real component
|
package/dist/types/index.d.ts
CHANGED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function HeartIcon(filled: boolean): any;
|
|
2
|
+
export declare function XIcon(): any;
|
|
3
|
+
export declare function ChevronDownIcon(): any;
|
|
4
|
+
export declare function ArrowRightLeftIcon(): any;
|
|
5
|
+
export declare function Trash2Icon(): any;
|
|
6
|
+
export declare function CheckIcon(): any;
|
|
7
|
+
export declare function CalendarPlusIcon(): any;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function tileHue(name: string): number;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared shapes crossing the library boundary.
|
|
3
|
+
*
|
|
4
|
+
* `DayOfWeek` and `RecipeSummary` mirror the app's own domain types
|
|
5
|
+
* (app/src/lib/domain) structurally. The library cannot import them from the
|
|
6
|
+
* app — it must build and publish independently, and the dependency only
|
|
7
|
+
* ever runs library -> app — so it declares its own copies here;
|
|
8
|
+
* TypeScript's structural typing makes the app's interfaces assignable to
|
|
9
|
+
* these without either package importing the other.
|
|
10
|
+
*
|
|
11
|
+
* `DayOption` and `PlannerEntry` have no equivalent elsewhere: they are
|
|
12
|
+
* genuinely owned by the library and re-exported for the app to import.
|
|
13
|
+
*/
|
|
14
|
+
export type DayOfWeek = 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat' | 'sun';
|
|
15
|
+
export type RecipeSource = 'api' | 'user';
|
|
16
|
+
export interface RecipeSummary {
|
|
17
|
+
id: string;
|
|
18
|
+
source: RecipeSource;
|
|
19
|
+
name: string;
|
|
20
|
+
thumbnail: string | null;
|
|
21
|
+
category: string | null;
|
|
22
|
+
area: string | null;
|
|
23
|
+
}
|
|
24
|
+
export interface DayOption {
|
|
25
|
+
day: DayOfWeek;
|
|
26
|
+
label: string;
|
|
27
|
+
occupied: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface PlannerEntry {
|
|
30
|
+
recipeId: string;
|
|
31
|
+
name: string;
|
|
32
|
+
thumbnail: string | null;
|
|
33
|
+
resolved: boolean;
|
|
34
|
+
}
|
package/package.json
CHANGED
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@ssingh465/recipe-ui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Stencil web component library for Recipe Finder & Meal Planner",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"repository": {
|
|
7
|
-
"type": "git",
|
|
8
|
-
"url": "https://github.com/ssingh465/recipe-finder-meal-planner.git",
|
|
9
|
-
"directory": "library"
|
|
10
|
-
},
|
|
11
|
-
"publishConfig": {
|
|
12
|
-
"access": "public"
|
|
13
|
-
},
|
|
14
|
-
"engines": {
|
|
15
|
-
"node": ">=20"
|
|
16
|
-
},
|
|
17
|
-
"type": "module",
|
|
18
|
-
"types": "./dist/components/index.d.ts",
|
|
19
|
-
"exports": {
|
|
20
|
-
".": {
|
|
21
|
-
"types": "./dist/components/index.d.ts",
|
|
22
|
-
"import": "./dist/components/index.js"
|
|
23
|
-
},
|
|
24
|
-
"./components": {
|
|
25
|
-
"types": "./dist/components/index.d.ts",
|
|
26
|
-
"import": "./dist/components/index.js"
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
"files": [
|
|
30
|
-
"dist"
|
|
31
|
-
],
|
|
32
|
-
"sideEffects": [
|
|
33
|
-
"dist/components/**"
|
|
34
|
-
],
|
|
35
|
-
"scripts": {
|
|
36
|
-
"build": "stencil build",
|
|
37
|
-
"start": "stencil build --dev --watch --serve",
|
|
38
|
-
"lint": "eslint src",
|
|
39
|
-
"test": "stencil-test --prod",
|
|
40
|
-
"test:watch": "stencil-test --prod --watch",
|
|
41
|
-
"generate": "stencil generate"
|
|
42
|
-
},
|
|
43
|
-
"dependencies": {
|
|
44
|
-
"@stencil/core": "^4.27.1 || ^5.0.0-0"
|
|
45
|
-
},
|
|
46
|
-
"devDependencies": {
|
|
47
|
-
"@eslint/js": "^10.0.1",
|
|
48
|
-
"@stencil/vitest": "^1.8.3",
|
|
49
|
-
"@types/node": "^22.13.5",
|
|
50
|
-
"@vitest/browser-playwright": "^4.0.0",
|
|
51
|
-
"eslint": "^10.9.0",
|
|
52
|
-
"playwright": "^1.52.0",
|
|
53
|
-
"typescript-eslint": "^8.68.0",
|
|
54
|
-
"vitest": "^4.0.0"
|
|
55
|
-
}
|
|
56
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@ssingh465/recipe-ui",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Stencil web component library for Recipe Finder & Meal Planner",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/ssingh465/recipe-finder-meal-planner.git",
|
|
9
|
+
"directory": "library"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=20"
|
|
16
|
+
},
|
|
17
|
+
"type": "module",
|
|
18
|
+
"types": "./dist/components/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/components/index.d.ts",
|
|
22
|
+
"import": "./dist/components/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./components": {
|
|
25
|
+
"types": "./dist/components/index.d.ts",
|
|
26
|
+
"import": "./dist/components/index.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"sideEffects": [
|
|
33
|
+
"dist/components/**"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "stencil build",
|
|
37
|
+
"start": "stencil build --dev --watch --serve",
|
|
38
|
+
"lint": "eslint src",
|
|
39
|
+
"test": "stencil-test --prod",
|
|
40
|
+
"test:watch": "stencil-test --prod --watch",
|
|
41
|
+
"generate": "stencil generate"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@stencil/core": "^4.27.1 || ^5.0.0-0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@eslint/js": "^10.0.1",
|
|
48
|
+
"@stencil/vitest": "^1.8.3",
|
|
49
|
+
"@types/node": "^22.13.5",
|
|
50
|
+
"@vitest/browser-playwright": "^4.0.0",
|
|
51
|
+
"eslint": "^10.9.0",
|
|
52
|
+
"playwright": "^1.52.0",
|
|
53
|
+
"typescript-eslint": "^8.68.0",
|
|
54
|
+
"vitest": "^4.0.0"
|
|
55
|
+
}
|
|
56
|
+
}
|