recipe-planner-ui 1.4.0 → 1.5.0
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/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/recipe-planner-ui.cjs.js +1 -1
- package/dist/cjs/rp-day-slot.cjs.entry.js +161 -17
- package/dist/cjs/rp-filter-chips_6.cjs.entry.js +611 -0
- package/dist/cjs/rp-filter-menu.cjs.entry.js +4 -4
- package/dist/collection/collection-manifest.json +1 -0
- package/dist/collection/components/rp-day-slot/rp-day-slot.css +25 -12
- package/dist/collection/components/rp-day-slot/rp-day-slot.js +163 -18
- package/dist/collection/components/rp-filter-chips/rp-filter-chips.js +1 -1
- package/dist/collection/components/rp-filter-menu/rp-filter-menu.js +4 -4
- package/dist/collection/components/rp-ingredient-list/rp-ingredient-list.js +2 -2
- package/dist/collection/components/rp-modal/rp-modal.css +20 -0
- package/dist/collection/components/rp-recipe-card/rp-recipe-card.css +2 -1
- package/dist/collection/components/rp-recipe-card/rp-recipe-card.js +2 -2
- package/dist/collection/components/rp-search-bar/rp-search-bar.css +33 -2
- package/dist/collection/components/rp-search-bar/rp-search-bar.js +1 -1
- package/dist/collection/components/rp-select/rp-select.css +205 -0
- package/dist/collection/components/rp-select/rp-select.js +559 -0
- package/dist/components/p-q8kKM7W4.js +1 -0
- package/dist/components/rp-day-slot.js +1 -1
- package/dist/components/rp-filter-chips.js +1 -1
- package/dist/components/rp-filter-menu.js +1 -1
- package/dist/components/rp-ingredient-list.js +1 -1
- package/dist/components/rp-modal.js +1 -1
- package/dist/components/rp-recipe-card.js +1 -1
- package/dist/components/rp-search-bar.js +1 -1
- package/dist/components/rp-select.d.ts +11 -0
- package/dist/components/rp-select.js +1 -0
- package/dist/esm/loader.js +1 -1
- package/dist/esm/recipe-planner-ui.js +1 -1
- package/dist/esm/rp-day-slot.entry.js +162 -18
- package/dist/esm/rp-filter-chips_6.entry.js +604 -0
- package/dist/esm/rp-filter-menu.entry.js +4 -4
- package/dist/recipe-planner-ui/{p-8f2706c2.entry.js → p-7a8ee97f.entry.js} +1 -1
- package/dist/recipe-planner-ui/p-7f595239.entry.js +1 -0
- package/dist/recipe-planner-ui/p-dcb370be.entry.js +1 -0
- package/dist/recipe-planner-ui/recipe-planner-ui.css +1 -1
- package/dist/recipe-planner-ui/recipe-planner-ui.esm.js +1 -1
- package/dist/types/components/rp-day-slot/rp-day-slot.d.ts +39 -3
- package/dist/types/components/rp-select/rp-select.d.ts +106 -0
- package/dist/types/components.d.ts +181 -2
- package/package.json +1 -1
- package/dist/cjs/rp-filter-chips_5.cjs.entry.js +0 -294
- package/dist/esm/rp-filter-chips_5.entry.js +0 -288
- package/dist/recipe-planner-ui/p-179f6d2f.entry.js +0 -1
- package/dist/recipe-planner-ui/p-a5bf7500.entry.js +0 -1
package/dist/cjs/loader.cjs.js
CHANGED
|
@@ -6,7 +6,7 @@ var appGlobals = require('./app-globals-V2Kpy_OQ.js');
|
|
|
6
6
|
const defineCustomElements = async (win, options) => {
|
|
7
7
|
if (typeof window === 'undefined') return undefined;
|
|
8
8
|
await appGlobals.globalScripts();
|
|
9
|
-
return index.bootstrapLazy([["rp-filter-
|
|
9
|
+
return index.bootstrapLazy([["rp-day-slot.cjs",[[774,"rp-day-slot",{"day":[513],"dayLabel":[1,"day-label"],"meals":[16],"days":[16],"dayLabels":[16],"normalized":[32],"dropActive":[32],"draggingId":[32]}]]],["rp-filter-menu.cjs",[[514,"rp-filter-menu",{"label":[1],"options":[16],"selected":[1],"searchPlaceholder":[1,"search-placeholder"],"searchable":[4],"open":[1540],"filterText":[32],"activeIndex":[32]},[[5,"pointerdown","onDocumentPointerDown"],[4,"keydown","onDocumentKeydown"]],{"open":[{"onOpenChange":0}]}]]],["rp-filter-chips_6.cjs",[[514,"rp-filter-chips",{"options":[16],"selected":[1],"label":[1]}],[774,"rp-ingredient-list",{"items":[16]}],[774,"rp-modal",{"open":[516],"heading":[1],"focusFirstField":[64]},null,{"open":[{"onOpenChange":0}]}],[774,"rp-recipe-card",{"recipeId":[1,"recipe-id"],"recipeTitle":[1,"recipe-title"],"image":[1],"category":[1],"href":[1],"hrefLabel":[1,"href-label"],"area":[1],"minutes":[2],"favorite":[516],"imageLoading":[32],"imageFailed":[32],"pulsing":[32]}],[514,"rp-search-bar",{"value":[1],"placeholder":[1],"label":[1],"draft":[32]},null,{"value":[{"onValueChange":0}]}],[514,"rp-select",{"options":[16],"value":[1025],"label":[1],"placeholder":[1],"open":[1540],"disabled":[516],"compact":[516],"dropUp":[1540,"drop-up"],"activeIndex":[32],"focusControl":[64]},[[5,"pointerdown","onDocumentPointerDown"]],{"open":[{"onOpenChange":0}],"options":[{"onOptionsChange":0}]}]]]], options);
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
exports.setNonce = index.setNonce;
|
|
@@ -19,7 +19,7 @@ var patchBrowser = () => {
|
|
|
19
19
|
|
|
20
20
|
patchBrowser().then(async (options) => {
|
|
21
21
|
await appGlobals.globalScripts();
|
|
22
|
-
return index.bootstrapLazy([["rp-filter-
|
|
22
|
+
return index.bootstrapLazy([["rp-day-slot.cjs",[[774,"rp-day-slot",{"day":[513],"dayLabel":[1,"day-label"],"meals":[16],"days":[16],"dayLabels":[16],"normalized":[32],"dropActive":[32],"draggingId":[32]}]]],["rp-filter-menu.cjs",[[514,"rp-filter-menu",{"label":[1],"options":[16],"selected":[1],"searchPlaceholder":[1,"search-placeholder"],"searchable":[4],"open":[1540],"filterText":[32],"activeIndex":[32]},[[5,"pointerdown","onDocumentPointerDown"],[4,"keydown","onDocumentKeydown"]],{"open":[{"onOpenChange":0}]}]]],["rp-filter-chips_6.cjs",[[514,"rp-filter-chips",{"options":[16],"selected":[1],"label":[1]}],[774,"rp-ingredient-list",{"items":[16]}],[774,"rp-modal",{"open":[516],"heading":[1],"focusFirstField":[64]},null,{"open":[{"onOpenChange":0}]}],[774,"rp-recipe-card",{"recipeId":[1,"recipe-id"],"recipeTitle":[1,"recipe-title"],"image":[1],"category":[1],"href":[1],"hrefLabel":[1,"href-label"],"area":[1],"minutes":[2],"favorite":[516],"imageLoading":[32],"imageFailed":[32],"pulsing":[32]}],[514,"rp-search-bar",{"value":[1],"placeholder":[1],"label":[1],"draft":[32]},null,{"value":[{"onValueChange":0}]}],[514,"rp-select",{"options":[16],"value":[1025],"label":[1],"placeholder":[1],"open":[1540],"disabled":[516],"compact":[516],"dropUp":[1540,"drop-up"],"activeIndex":[32],"focusControl":[64]},[[5,"pointerdown","onDocumentPointerDown"]],{"open":[{"onOpenChange":0}],"options":[{"onOptionsChange":0}]}]]]], options);
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
exports.setNonce = index.setNonce;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var index = require('./index-CrJlwqe_.js');
|
|
4
4
|
|
|
5
|
-
const rpDaySlotCss = () => `.sc-rp-day-slot-h{display:block;font-family:var(--rp-font-sans);color:var(--rp-color-text)}.slot.sc-rp-day-slot{display:flex;flex-direction:column;height:100%;min-height:200px;overflow:hidden;background:var(--rp-color-surface);border:1px solid var(--rp-color-border);border-radius:var(--rp-radius-lg);box-shadow:var(--rp-shadow-xs);transition:border-color var(--rp-duration) var(--rp-ease), box-shadow var(--rp-duration) var(--rp-ease), background-color var(--rp-duration) var(--rp-ease)}.slot.is-empty.sc-rp-day-slot{background:var(--rp-color-surface-sunken);border-style:dashed}.slot.is-drop-active.sc-rp-day-slot{background:var(--rp-color-highlight-soft);border-style:solid;border-color:var(--rp-color-highlight);box-shadow:var(--rp-shadow-md), inset 0 0 0 1px var(--rp-color-highlight)}.head.sc-rp-day-slot{display:flex;gap:var(--rp-space-1);align-items:center;justify-content:space-between;padding:var(--rp-space-3) var(--rp-space-2) var(--rp-space-3) var(--rp-space-3);border-bottom:1px solid var(--rp-color-border)}.day.sc-rp-day-slot{min-width:0;margin:0;overflow:hidden;font-size:var(--rp-font-size-xs);font-weight:700;letter-spacing:0.08em;text-transform:uppercase;text-overflow:ellipsis;white-space:nowrap;color:var(--rp-color-text-muted)}.head-right.sc-rp-day-slot{display:flex;flex-shrink:0;gap:var(--rp-space-2);align-items:center}.count.sc-rp-day-slot{display:grid;place-items:center;min-width:20px;height:20px;padding:0 6px;font-size:0.6875rem;font-weight:700;font-variant-numeric:tabular-nums;color:var(--rp-caramel-600);background:var(--rp-color-highlight-soft);border-radius:var(--rp-radius-pill)}.add.sc-rp-day-slot{display:grid;place-items:center;width:26px;height:26px;padding:0;cursor:pointer;color:var(--rp-color-accent-contrast);background:var(--rp-color-accent);border:none;border-radius:50%;transition:transform var(--rp-duration-fast) var(--rp-ease), background-color var(--rp-duration-fast) var(--rp-ease)}.add.sc-rp-day-slot svg.sc-rp-day-slot{fill:none;stroke:currentColor;stroke-width:2.2;stroke-linecap:round}.add.sc-rp-day-slot:hover{background:var(--rp-color-highlight);transform:rotate(90deg) scale(1.1)}.add.sc-rp-day-slot:active{transform:rotate(90deg) scale(0.92)}.meals.sc-rp-day-slot{flex:1;margin:0;padding:var(--rp-space-2);list-style:none;display:flex;flex-direction:column;gap:var(--rp-space-2)}.meal.sc-rp-day-slot{position:relative;padding:var(--rp-space-3) var(--rp-space-3) var(--rp-space-2);cursor:grab;background:var(--rp-color-surface-sunken);border:1px solid var(--rp-color-border);border-radius:var(--rp-radius-md);transition:transform var(--rp-duration-fast) var(--rp-ease), box-shadow var(--rp-duration-fast) var(--rp-ease), opacity var(--rp-duration-fast) var(--rp-ease)}.meal.sc-rp-day-slot:hover{background:var(--rp-color-surface);box-shadow:var(--rp-shadow-sm);transform:translateY(-1px)}.meal.sc-rp-day-slot:active{cursor:grabbing}.meal.is-dragging.sc-rp-day-slot{opacity:0.4;box-shadow:none;transform:none}.grip.sc-rp-day-slot{position:absolute;inset-block-start:var(--rp-space-2);inset-inline-end:var(--rp-space-2);display:block;opacity:0;transition:opacity var(--rp-duration-fast) var(--rp-ease)}.grip.sc-rp-day-slot svg.sc-rp-day-slot{display:block;fill:var(--rp-color-text-subtle)}.meal.sc-rp-day-slot:hover .grip.sc-rp-day-slot{opacity:1}.meal-title.sc-rp-day-slot{display:block;padding-inline-end:var(--rp-space-4);font-size:var(--rp-font-size-sm);font-weight:500;line-height:1.35;color:var(--rp-color-text)}.meal-actions.sc-rp-day-slot{display:flex;gap:var(--rp-space-1);align-items:center;justify-content:space-between;margin-top:var(--rp-space-2)}.move-select.sc-rp-day-slot{
|
|
5
|
+
const rpDaySlotCss = () => `.sc-rp-day-slot-h{display:block;font-family:var(--rp-font-sans);color:var(--rp-color-text)}.slot.sc-rp-day-slot{display:flex;flex-direction:column;height:100%;min-height:200px;overflow:hidden;background:var(--rp-color-surface);border:1px solid var(--rp-color-border);border-radius:var(--rp-radius-lg);box-shadow:var(--rp-shadow-xs);transition:border-color var(--rp-duration) var(--rp-ease), box-shadow var(--rp-duration) var(--rp-ease), background-color var(--rp-duration) var(--rp-ease)}.slot.is-empty.sc-rp-day-slot{background:var(--rp-color-surface-sunken);border-style:dashed}.slot.is-drop-active.sc-rp-day-slot{background:var(--rp-color-highlight-soft);border-style:solid;border-color:var(--rp-color-highlight);box-shadow:var(--rp-shadow-md), inset 0 0 0 1px var(--rp-color-highlight)}.head.sc-rp-day-slot{display:flex;gap:var(--rp-space-1);align-items:center;justify-content:space-between;padding:var(--rp-space-3) var(--rp-space-2) var(--rp-space-3) var(--rp-space-3);border-bottom:1px solid var(--rp-color-border)}.day.sc-rp-day-slot{min-width:0;margin:0;overflow:hidden;font-size:var(--rp-font-size-xs);font-weight:700;letter-spacing:0.08em;text-transform:uppercase;text-overflow:ellipsis;white-space:nowrap;color:var(--rp-color-text-muted)}.head-right.sc-rp-day-slot{display:flex;flex-shrink:0;gap:var(--rp-space-2);align-items:center}.count.sc-rp-day-slot{display:grid;place-items:center;min-width:20px;height:20px;padding:0 6px;font-size:0.6875rem;font-weight:700;font-variant-numeric:tabular-nums;color:var(--rp-caramel-600);background:var(--rp-color-highlight-soft);border-radius:var(--rp-radius-pill)}.add.sc-rp-day-slot{display:grid;place-items:center;width:26px;height:26px;padding:0;cursor:pointer;color:var(--rp-color-accent-contrast);background:var(--rp-color-accent);border:none;border-radius:50%;transition:transform var(--rp-duration-fast) var(--rp-ease), background-color var(--rp-duration-fast) var(--rp-ease)}.add.sc-rp-day-slot svg.sc-rp-day-slot{fill:none;stroke:currentColor;stroke-width:2.2;stroke-linecap:round}.add.sc-rp-day-slot:hover{background:var(--rp-color-highlight);transform:rotate(90deg) scale(1.1)}.add.sc-rp-day-slot:active{transform:rotate(90deg) scale(0.92)}.meals.sc-rp-day-slot{flex:1;margin:0;padding:var(--rp-space-2);list-style:none;display:flex;flex-direction:column;gap:var(--rp-space-2)}.meal.sc-rp-day-slot{position:relative;padding:var(--rp-space-3) var(--rp-space-3) var(--rp-space-2);cursor:grab;touch-action:pan-y;background:var(--rp-color-surface-sunken);border:1px solid var(--rp-color-border);border-radius:var(--rp-radius-md);transition:transform var(--rp-duration-fast) var(--rp-ease), box-shadow var(--rp-duration-fast) var(--rp-ease), opacity var(--rp-duration-fast) var(--rp-ease)}.meal.sc-rp-day-slot:hover{background:var(--rp-color-surface);box-shadow:var(--rp-shadow-sm);transform:translateY(-1px)}.meal.sc-rp-day-slot:active{cursor:grabbing}.meal.is-dragging.sc-rp-day-slot{opacity:0.4;box-shadow:none;transform:none}.grip.sc-rp-day-slot{position:absolute;inset-block-start:var(--rp-space-2);inset-inline-end:var(--rp-space-2);display:block;opacity:0;transition:opacity var(--rp-duration-fast) var(--rp-ease)}.grip.sc-rp-day-slot svg.sc-rp-day-slot{display:block;fill:var(--rp-color-text-subtle)}.meal.sc-rp-day-slot:hover .grip.sc-rp-day-slot{opacity:1}.meal-title.sc-rp-day-slot{display:block;padding-inline-end:var(--rp-space-4);font-size:var(--rp-font-size-sm);font-weight:500;line-height:1.35;color:var(--rp-color-text)}.meal-actions.sc-rp-day-slot{display:flex;gap:var(--rp-space-1);align-items:center;justify-content:space-between;margin-top:var(--rp-space-2)}.move-select.sc-rp-day-slot{min-width:0;position:relative;z-index:1}.meal.sc-rp-day-slot:has(rp-select[open]),.meals.sc-rp-day-slot:has(rp-select[open]){overflow:visible}.slot.sc-rp-day-slot:has(rp-select[open]){overflow:visible}.meal.sc-rp-day-slot:has(rp-select[open]){z-index:3}.remove.sc-rp-day-slot{display:grid;place-items:center;width:24px;height:24px;padding:0;color:var(--rp-color-text-subtle);cursor:pointer;background:none;border:none;border-radius:var(--rp-radius-sm);transition:color var(--rp-duration-fast) var(--rp-ease), background-color var(--rp-duration-fast) var(--rp-ease)}.remove.sc-rp-day-slot svg.sc-rp-day-slot{fill:none;stroke:currentColor;stroke-width:2.2;stroke-linecap:round}.remove.sc-rp-day-slot:hover{color:var(--rp-color-danger);background:var(--rp-color-danger-soft)}.empty.sc-rp-day-slot{display:flex;flex:1;flex-direction:column;gap:var(--rp-space-2);align-items:center;justify-content:center;padding:var(--rp-space-4) var(--rp-space-3);text-align:center}.empty-icon.sc-rp-day-slot{fill:none;stroke:var(--rp-color-text-subtle);stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}.empty-text.sc-rp-day-slot{margin:0;font-size:var(--rp-font-size-xs);color:var(--rp-color-text-subtle)}.add.sc-rp-day-slot:focus-visible,.remove.sc-rp-day-slot:focus-visible,.move-select.sc-rp-day-slot:focus-visible{outline:var(--rp-focus-ring);outline-offset:var(--rp-focus-offset)}.sr-only.sc-rp-day-slot{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}@media (prefers-reduced-motion: reduce){.slot.sc-rp-day-slot,.meal.sc-rp-day-slot,.add.sc-rp-day-slot,.grip.sc-rp-day-slot,.remove.sc-rp-day-slot,.move-select.sc-rp-day-slot{transition:none}.meal.sc-rp-day-slot:hover,.add.sc-rp-day-slot:hover,.add.sc-rp-day-slot:active{transform:none}.grip.sc-rp-day-slot{opacity:1}}`;
|
|
6
6
|
|
|
7
7
|
const DaySlot = class {
|
|
8
8
|
constructor(hostRef) {
|
|
@@ -11,7 +11,9 @@ const DaySlot = class {
|
|
|
11
11
|
this.rpRemoveMeal = index.createEvent(this, "rpRemoveMeal");
|
|
12
12
|
this.rpMoveMeal = index.createEvent(this, "rpMoveMeal");
|
|
13
13
|
}
|
|
14
|
-
|
|
14
|
+
get el() { return index.getElement(this); }
|
|
15
|
+
/** Machine-readable day key, echoed in every event. Reflected so a pointer drag can
|
|
16
|
+
* read the target column straight off the element it lands on. */
|
|
15
17
|
day;
|
|
16
18
|
/** Human-readable day name shown as the column heading. */
|
|
17
19
|
dayLabel = '';
|
|
@@ -36,6 +38,22 @@ const DaySlot = class {
|
|
|
36
38
|
* flicker off mid-column. Counting enter and leave pairs is what makes the state stable.
|
|
37
39
|
*/
|
|
38
40
|
dragDepth = 0;
|
|
41
|
+
/**
|
|
42
|
+
* Pointer-drag state, used on touch.
|
|
43
|
+
*
|
|
44
|
+
* HTML5 drag-and-drop is a mouse-only API — a touch never produces `dragstart`, so on a
|
|
45
|
+
* phone or tablet the meals simply could not be dragged. Pointer events cover mouse,
|
|
46
|
+
* touch, and pen from one code path, so this runs alongside the native drag rather than
|
|
47
|
+
* replacing it: the native path keeps working where the browser provides it, and this
|
|
48
|
+
* takes over where it does not.
|
|
49
|
+
*/
|
|
50
|
+
pointerDrag = null;
|
|
51
|
+
/** The floating copy that follows the finger, appended to the document while dragging. */
|
|
52
|
+
ghost = null;
|
|
53
|
+
/** The panel currently highlighted under the pointer, so it can be un-highlighted. */
|
|
54
|
+
hoveredPanel = null;
|
|
55
|
+
/** Movement in CSS pixels before a press becomes a drag rather than a tap or a scroll. */
|
|
56
|
+
static DRAG_THRESHOLD = 8;
|
|
39
57
|
/** Fired when the user asks to add a meal to this day. */
|
|
40
58
|
rpAddMeal;
|
|
41
59
|
/** Fired when a meal is removed from this day. */
|
|
@@ -50,18 +68,34 @@ const DaySlot = class {
|
|
|
50
68
|
// replaced before the element upgrades, which @Watch would miss.
|
|
51
69
|
this.normalized = Array.isArray(this.meals) ? this.meals : [];
|
|
52
70
|
}
|
|
71
|
+
connectedCallback() {
|
|
72
|
+
/**
|
|
73
|
+
* Every column clears its own highlight when any drag anywhere ends.
|
|
74
|
+
*
|
|
75
|
+
* `dragleave` is not guaranteed: a drop, or a drag abandoned with Escape, can leave a
|
|
76
|
+
* column highlighted with no further event coming to it. Listening for the global end
|
|
77
|
+
* of the gesture is what makes the reset unconditional.
|
|
78
|
+
*/
|
|
79
|
+
document.addEventListener('dragend', this.clearDragState);
|
|
80
|
+
document.addEventListener('drop', this.clearDragState);
|
|
81
|
+
}
|
|
82
|
+
disconnectedCallback() {
|
|
83
|
+
// A pointer drag registers listeners on `window` and appends a ghost to the body,
|
|
84
|
+
// both of which would outlive the component if it unmounted mid-drag.
|
|
85
|
+
this.endPointerDrag();
|
|
86
|
+
document.removeEventListener('dragend', this.clearDragState);
|
|
87
|
+
document.removeEventListener('drop', this.clearDragState);
|
|
88
|
+
}
|
|
89
|
+
clearDragState = () => {
|
|
90
|
+
this.dragDepth = 0;
|
|
91
|
+
this.dropActive = false;
|
|
92
|
+
this.draggingId = null;
|
|
93
|
+
};
|
|
53
94
|
emitMove(id, from, to) {
|
|
54
95
|
if (!to || to === from)
|
|
55
96
|
return;
|
|
56
97
|
this.rpMoveMeal.emit({ id, from, to });
|
|
57
98
|
}
|
|
58
|
-
onMove = (event, meal) => {
|
|
59
|
-
const select = event.target;
|
|
60
|
-
this.emitMove(meal.id, this.day, select.value);
|
|
61
|
-
// The consumer re-renders from its own data; resetting avoids a stale selection
|
|
62
|
-
// if that render is delayed.
|
|
63
|
-
select.value = '';
|
|
64
|
-
};
|
|
65
99
|
onDragStart = (event, meal) => {
|
|
66
100
|
this.draggingId = meal.id;
|
|
67
101
|
// A custom MIME type is what lets dragover distinguish a meal from a dragged file or
|
|
@@ -72,11 +106,10 @@ const DaySlot = class {
|
|
|
72
106
|
event.dataTransfer?.setData('text/plain', meal.title);
|
|
73
107
|
if (event.dataTransfer)
|
|
74
108
|
event.dataTransfer.effectAllowed = 'move';
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
this
|
|
78
|
-
|
|
79
|
-
this.dropActive = false;
|
|
109
|
+
// Cleanup is handled by the document-level `dragend`/`drop` listeners registered in
|
|
110
|
+
// `connectedCallback`. The element's own `dragend` cannot be relied on: a successful
|
|
111
|
+
// move re-renders this column, so the source element is gone before the event would
|
|
112
|
+
// have reached it, leaving the card stuck at 40% opacity.
|
|
80
113
|
};
|
|
81
114
|
hasMealPayload(event) {
|
|
82
115
|
return event.dataTransfer?.types.includes('application/x-rp-meal') ?? false;
|
|
@@ -103,6 +136,117 @@ const DaySlot = class {
|
|
|
103
136
|
if (this.dragDepth === 0)
|
|
104
137
|
this.dropActive = false;
|
|
105
138
|
};
|
|
139
|
+
/**
|
|
140
|
+
* Begins tracking a possible pointer drag.
|
|
141
|
+
*
|
|
142
|
+
* Nothing happens until the pointer has moved past the threshold, so a tap still reaches
|
|
143
|
+
* the select and the remove button, and a vertical swipe still scrolls the page.
|
|
144
|
+
*/
|
|
145
|
+
onPointerDown = (event, meal) => {
|
|
146
|
+
/**
|
|
147
|
+
* Touch and pen only. A mouse already has HTML5 drag-and-drop, and running both from
|
|
148
|
+
* one gesture makes them fight: this path's `preventDefault` on pointermove — needed
|
|
149
|
+
* to stop a touch scrolling the page — also suppresses the native drag's own feedback,
|
|
150
|
+
* so the card never dimmed and no drag image appeared even though the drop still
|
|
151
|
+
* worked. The native path is left to own the mouse entirely.
|
|
152
|
+
*/
|
|
153
|
+
if (event.pointerType === 'mouse')
|
|
154
|
+
return;
|
|
155
|
+
// A press on the select or a button is that control's, not the start of a drag.
|
|
156
|
+
if (event.target.closest('button, select, label, a, rp-select'))
|
|
157
|
+
return;
|
|
158
|
+
this.pointerDrag = {
|
|
159
|
+
id: meal.id,
|
|
160
|
+
title: meal.title,
|
|
161
|
+
pointerId: event.pointerId,
|
|
162
|
+
startX: event.clientX,
|
|
163
|
+
startY: event.clientY,
|
|
164
|
+
started: false,
|
|
165
|
+
};
|
|
166
|
+
window.addEventListener('pointermove', this.onPointerMove, { passive: false });
|
|
167
|
+
window.addEventListener('pointerup', this.onPointerUp);
|
|
168
|
+
window.addEventListener('pointercancel', this.onPointerCancel);
|
|
169
|
+
};
|
|
170
|
+
onPointerMove = (event) => {
|
|
171
|
+
const drag = this.pointerDrag;
|
|
172
|
+
if (!drag || event.pointerId !== drag.pointerId)
|
|
173
|
+
return;
|
|
174
|
+
if (!drag.started) {
|
|
175
|
+
const moved = Math.hypot(event.clientX - drag.startX, event.clientY - drag.startY);
|
|
176
|
+
if (moved < DaySlot.DRAG_THRESHOLD)
|
|
177
|
+
return;
|
|
178
|
+
drag.started = true;
|
|
179
|
+
this.draggingId = drag.id;
|
|
180
|
+
this.createGhost(drag.title);
|
|
181
|
+
}
|
|
182
|
+
// Suppresses the page scroll that a touch-move would otherwise produce.
|
|
183
|
+
event.preventDefault();
|
|
184
|
+
this.moveGhost(event.clientX, event.clientY);
|
|
185
|
+
this.highlightPanelAt(event.clientX, event.clientY);
|
|
186
|
+
};
|
|
187
|
+
onPointerUp = (event) => {
|
|
188
|
+
const drag = this.pointerDrag;
|
|
189
|
+
if (!drag || event.pointerId !== drag.pointerId)
|
|
190
|
+
return;
|
|
191
|
+
const started = drag.started;
|
|
192
|
+
const id = drag.id;
|
|
193
|
+
this.endPointerDrag();
|
|
194
|
+
if (!started)
|
|
195
|
+
return;
|
|
196
|
+
const panel = this.panelAt(event.clientX, event.clientY);
|
|
197
|
+
const to = panel?.getAttribute('day');
|
|
198
|
+
if (to)
|
|
199
|
+
this.emitMove(id, this.day, to);
|
|
200
|
+
};
|
|
201
|
+
onPointerCancel = () => this.endPointerDrag();
|
|
202
|
+
endPointerDrag() {
|
|
203
|
+
this.pointerDrag = null;
|
|
204
|
+
this.draggingId = null;
|
|
205
|
+
this.clearHighlight();
|
|
206
|
+
this.ghost?.remove();
|
|
207
|
+
this.ghost = null;
|
|
208
|
+
window.removeEventListener('pointermove', this.onPointerMove);
|
|
209
|
+
window.removeEventListener('pointerup', this.onPointerUp);
|
|
210
|
+
window.removeEventListener('pointercancel', this.onPointerCancel);
|
|
211
|
+
}
|
|
212
|
+
/** The `rp-day-slot` under a point, found by hit-testing rather than by geometry. */
|
|
213
|
+
panelAt(x, y) {
|
|
214
|
+
// The ghost follows the pointer and would otherwise be the topmost element.
|
|
215
|
+
if (this.ghost)
|
|
216
|
+
this.ghost.style.display = 'none';
|
|
217
|
+
const target = document.elementFromPoint(x, y);
|
|
218
|
+
if (this.ghost)
|
|
219
|
+
this.ghost.style.display = '';
|
|
220
|
+
return target?.closest('rp-day-slot') ?? null;
|
|
221
|
+
}
|
|
222
|
+
highlightPanelAt(x, y) {
|
|
223
|
+
const panel = this.panelAt(x, y);
|
|
224
|
+
if (panel === this.hoveredPanel)
|
|
225
|
+
return;
|
|
226
|
+
this.clearHighlight();
|
|
227
|
+
// The origin column is not a drop target; highlighting it would suggest otherwise.
|
|
228
|
+
if (panel && panel !== this.el) {
|
|
229
|
+
panel.querySelector('.slot')?.classList.add('is-drop-active');
|
|
230
|
+
this.hoveredPanel = panel;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
clearHighlight() {
|
|
234
|
+
this.hoveredPanel?.querySelector('.slot')?.classList.remove('is-drop-active');
|
|
235
|
+
this.hoveredPanel = null;
|
|
236
|
+
}
|
|
237
|
+
createGhost(title) {
|
|
238
|
+
const ghost = document.createElement('div');
|
|
239
|
+
ghost.textContent = title;
|
|
240
|
+
ghost.setAttribute('aria-hidden', 'true');
|
|
241
|
+
ghost.className = 'rp-drag-ghost';
|
|
242
|
+
document.body.append(ghost);
|
|
243
|
+
this.ghost = ghost;
|
|
244
|
+
}
|
|
245
|
+
moveGhost(x, y) {
|
|
246
|
+
if (!this.ghost)
|
|
247
|
+
return;
|
|
248
|
+
this.ghost.style.transform = `translate(${x}px, ${y}px)`;
|
|
249
|
+
}
|
|
106
250
|
onDrop = (event) => {
|
|
107
251
|
const payload = event.dataTransfer?.getData('application/x-rp-meal');
|
|
108
252
|
if (!payload)
|
|
@@ -123,9 +267,9 @@ const DaySlot = class {
|
|
|
123
267
|
const days = Array.isArray(this.days) ? this.days : [];
|
|
124
268
|
const labels = this.dayLabels ?? {};
|
|
125
269
|
const meals = this.normalized;
|
|
126
|
-
return (index.h(index.Host, { key: '
|
|
127
|
-
|
|
128
|
-
|
|
270
|
+
return (index.h(index.Host, { key: '47e4ac959151a9e43b8398bcbed2df2af827e9f8' }, index.h("section", { key: 'b08414f465303f8015bc3035e3f12994c1868ac8', class: { slot: true, 'is-drop-active': this.dropActive, 'is-empty': meals.length === 0 }, onDragEnter: this.onDragEnter, onDragOver: this.onDragOver, onDragLeave: this.onDragLeave, onDrop: this.onDrop }, index.h("header", { key: '1b3ab376369ae3339e60740693bcf349ef14a981', class: "head" }, index.h("h3", { key: '077f5818cc37c85110ec831482f6f700ada577dc', class: "day" }, this.dayLabel || this.day), index.h("div", { key: '1fabc2004094d0787d6d734b05d95d825c41c55f', class: "head-right" }, meals.length > 0 && index.h("span", { key: 'af3875556f999563482e8e9ecc833e768515f001', class: "count" }, meals.length), index.h("button", { key: '56f11a2a992f8467d4c876d7ab0771f0f89f31e3', type: "button", class: "add", onClick: () => this.rpAddMeal.emit(this.day), "aria-label": `Add a meal to ${(this.dayLabels ?? {})[this.day] || this.dayLabel || this.day}` }, index.h("svg", { key: 'c27fc88bd1ef85ddac15016e21ca140ffd7d0f82', viewBox: "0 0 24 24", width: "15", height: "15", "aria-hidden": "true" }, index.h("path", { key: 'ecde1e8d37b1c6d2abc9a7edc81ec39d09f79d84', d: "M12 5v14M5 12h14" }))))), meals.length === 0 ? (index.h("div", { class: "empty" }, index.h("slot", null, index.h("svg", { class: "empty-icon", viewBox: "0 0 24 24", width: "26", height: "26", "aria-hidden": "true" }, index.h("path", { d: "M4 4h16v16H4z", opacity: "0.35" }), index.h("path", { d: "M8 2v4M16 2v4M4 10h16" })), index.h("p", { class: "empty-text" }, "No meals planned")))) : (index.h("ul", { class: "meals" }, meals.map((meal) => (index.h("li", { class: { meal: true, 'is-dragging': this.draggingId === meal.id }, key: meal.id, draggable: true, onDragStart: (event) => this.onDragStart(event, meal), onPointerDown: (event) => this.onPointerDown(event, meal) }, index.h("span", { class: "grip", "aria-hidden": "true" }, index.h("svg", { viewBox: "0 0 24 24", width: "12", height: "12" }, index.h("circle", { cx: "9", cy: "6", r: "1.4" }), index.h("circle", { cx: "15", cy: "6", r: "1.4" }), index.h("circle", { cx: "9", cy: "12", r: "1.4" }), index.h("circle", { cx: "15", cy: "12", r: "1.4" }), index.h("circle", { cx: "9", cy: "18", r: "1.4" }), index.h("circle", { cx: "15", cy: "18", r: "1.4" }))), index.h("span", { class: "meal-title" }, meal.title), index.h("div", { class: "meal-actions" }, index.h("rp-select", { class: "move-select", compact: true, label: `Move ${meal.title} to another day`, placeholder: "Move\u2026", options: days
|
|
271
|
+
.filter((day) => day !== this.day)
|
|
272
|
+
.map((day) => ({ value: day, label: labels[day] ?? day })), onRpSelectChange: (event) => this.emitMove(meal.id, this.day, event.detail) }), index.h("button", { type: "button", class: "remove", onClick: () => this.rpRemoveMeal.emit({ id: meal.id, day: this.day }), "aria-label": `Remove ${meal.title}` }, index.h("svg", { viewBox: "0 0 24 24", width: "13", height: "13", "aria-hidden": "true" }, index.h("path", { d: "M6 6l12 12M18 6L6 18" }))))))))))));
|
|
129
273
|
}
|
|
130
274
|
};
|
|
131
275
|
DaySlot.style = rpDaySlotCss();
|