@magic-spells/cart

A cart that sells

Cart components for Shopify: a progress bar that unlocks free shipping, gifts that add themselves at a threshold, and quantity updates that never keep the customer waiting.

Install npm install @magic-spells/cart-panel
4.9 kB · 2.8 kB panel alone min + gzip
The cart

Open it.

Quantities and removals run through a simulated updateCartItem(), delay and all.

Items
0
Subtotal
$0.00
Render modes

Two ways to draw a line.

By default the panel renders line items from a JS template. Point section at a Shopify section and the server renders them instead — same selectors, same events, same states, and the markup rides along in the mutation response. Add optimistic and quantities move on the click, not on the answer.

Lines drawn by
JS template
Requests in flight
0
<!-- server renders content, JS renders behavior --> <cart-panel section="API-cart-items" optimistic>
Entry points

One line, whole cart.

Panel and item are designed as a set, so the root import registers both. Take them à la carte if you'd rather — the panel alone still renders count, subtotal and empty state, it just skips the items.

// <cart-panel>, <cart-item>, <cart-item-content>, <cart-item-processing> import '@magic-spells/cart-panel'; import '@magic-spells/cart-panel/css'; // à la carte import '@magic-spells/cart-panel/panel'; import '@magic-spells/cart-panel/cart-item';
The set

It doesn't work alone.

Two sibling packages sit inside the panel and listen to cart-panel:data-changed: a shipping bar that fills to $150, and a gift that adds itself to the cart at $200 and takes itself back out below it. Open the cart and add items.

import '@magic-spells/cart-panel'; import '@magic-spells/cart-progress-bar'; import '@magic-spells/gift-with-purchase';
Events

Everything announces.

Reference

The full reference.

Panel attributes

Attribute
Default
Description
manual
absent
Skip the refresh on connect. You call refreshCart() yourself.
section
absent
Shopify section id that renders the line items. Absent, a JS template renders them.
optimistic
absent
Apply quantity changes and removals locally first, then send them. No processing state.
hide-count-when-empty
absent
Hide every [data-content-cart-count] element, page-wide, while the cart is empty.
state
reflected
has-items or empty. Drives [data-cart-has-items] and [data-cart-is-empty].

Panel methods

Method
Returns
Description
show(trigger?, cart?)
void
Opens the nearest <dialog-panel> ancestor.
hide()
void
Closes it. Also bound to [data-action-hide-cart].
getCart()
Promise
Fetches /cart.json. Override it to fake the cart — this demo fakes the endpoint instead.
getCartSection()
Promise
Fetches /?sections=<id> in section mode. Resolves to null without one.
updateCartItem(key, qty)
Promise
POSTs /cart/change.json. qty: 0 removes. In section mode it asks for the markup too.
refreshCart(cart?)
Promise
Renders the given cart, or fetches one first.
setCartItemTemplate(name, fn)
void
Line-item markup for a template name. Items pick one with _cart_template, else default.
setCartItemProcessingTemplate(fn)
void
The overlay drawn while a line is in flight.
on(event, fn) / off()
this
Chainable subscription, the same events as below.

Panel events

Event
Detail
Fires
cart-panel:show
{ triggerElement }
On show().
cart-panel:hide
On hide().
cart-panel:refreshed
{ cart }
After a refresh.
cart-panel:updated
{ cart }
After a quantity change.
cart-panel:data-changed
cart + calculated_count, calculated_subtotal
Any change. The one the progress bar and the gift listen to. Optimistic updates fire it on the click.
cart-panel:error
{ key, error }
An optimistic change the server refused. Server truth is already back on screen.

Selectors

Selector
Where
Role
[data-content-cart-items]
Panel
Container the items render into.
[data-cart-has-items]
[data-cart-is-empty]
Panel
Shown and hidden by state.
[data-content-cart-count]
[data-content-cart-subtotal]
Anywhere
Written on every change, page-wide.
[data-action-hide-cart]
Panel
Close buttons.
[data-action-remove-item]
[data-cart-quantity]
[data-content-line-price]
Item template or section
Remove button, quantity field, formatted line price. Both render modes bind to the same three.

Line item properties

Property
Effect
Used by
_hide_in_cart
In the cart, out of the list.
Gifts, bundle children.
_ignore_price_in_subtotal
Out of calculated_subtotal.
Gifts, so they can't unlock themselves.
_cart_template
Picks a named template.
Anything that renders differently.
_group_id / _group_role
Groups lines together.
Bundles.

Cart item

Member
Kind
Description
CartItem.setTemplate()
CartItem.setProcessingTemplate()
CartItem.createAnimated()
Static
Templates globally, and one item built mid-animation.
setState() · setData() · destroyYourself()
Instance
Drive one line by hand.
setContent(html)
applyItemData(item)
Instance
Section mode: swap in server markup keeping identity and focus, or move the numbers from JSON alone.
ready · processing · destroying · appearing
States
On the state attribute. Blur, scale and height come from CSS.
cart-item:remove
cart-item:quantity-change
Events
Bubble to the panel, which does the AJAX.

CSS custom properties

Property
Default
Description
--cart-item-loader-color
#000
The processing dots. Jade here.
--cart-item-processing-duration
--cart-item-destroying-duration
--cart-item-appearing-duration
250ms · 600ms · 400ms
One per state transition.
--cart-item-processing-scale
--cart-item-destroying-scale
0.98 · 0.85
How far a line shrinks.
--cart-item-processing-blur
--cart-item-destroying-blur
1px · 10px
And how far it goes out of focus.
--cart-item-shadow-color
rgba(0, 0, 0, 0.15)
The lift under a line in flight.

Cart

Travel Candle

Your cart is empty

Subtotal $0.00