-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 46b9434
Showing
25 changed files
with
7,941 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
[*] | ||
end_of_line = lf | ||
insert_final_newline = true | ||
indent_style = space | ||
indent_size = 2 | ||
quote_type = single |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# Logs | ||
logs | ||
*.log | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
lerna-debug.log* | ||
|
||
# Diagnostic reports (https://nodejs.org/api/report.html) | ||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json | ||
|
||
# Directory for instrumented libs generated by jscoverage/JSCover | ||
lib-cov | ||
|
||
# Coverage directory used by tools like istanbul | ||
coverage | ||
*.lcov | ||
|
||
# Dependency directories | ||
node_modules/ | ||
jspm_packages/ | ||
|
||
# TypeScript v1 declaration files | ||
typings/ | ||
|
||
# TypeScript cache | ||
*.tsbuildinfo | ||
|
||
# Optional npm cache directory | ||
.npm | ||
|
||
# Optional eslint cache | ||
.eslintcache | ||
|
||
meta.json | ||
|
||
*.tgz |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"editor.codeActionsOnSave": { | ||
"source.fixAll.eslint": "explicit" | ||
}, | ||
"eslint.validate": [ | ||
"javascript" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Pico State Manager | ||
|
||
Live demo: https://yesil.github.io/picosm | ||
|
||
## Introduction | ||
|
||
This is an experimental lightweight, zero dependency state manager that attempts to replicate core MobX features without using Proxy objects. | ||
|
||
It also provides a helper function to make Lit components observers and updates them on changes. | ||
|
||
Currently, the non minified bundle size is `4938 bytes` and is around `~1.2kb` gzipped. | ||
|
||
## Partially Supported Features | ||
|
||
- `makeObservable` (instance level change detection) | ||
- `observe` (callback when the instance of an observable class changes) | ||
- `reaction` (react to specific changes) | ||
- `computed` (cache computed values) | ||
|
||
Also, an opinionated approach for tracking nested dependencies with `track/untrack` functions is added. If an observable wants to get notified by another one, these functions can be used. | ||
|
||
## Why Yet Another State Manager | ||
|
||
Libraries like Redux, Zustand, and Nanostores treat the state as a typeless object and require updating to the next version of the state manually. This results in verbose code with application logic scattered around in various functions, making it hard to understand. | ||
|
||
## How to Use | ||
|
||
See the demo app source code: https://github.com/yesil/picosm/tree/main/demo-app | ||
|
||
## Contributing | ||
|
||
Please feel free to: | ||
|
||
Open a PR to contribute. | ||
Create an issue to request a feature. | ||
|
||
``` | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
import { LitElement, html, css } from 'https://jspm.dev/npm:lit@2'; | ||
import { StoreObservable } from './model.js'; | ||
import { makeObserver, reaction } from '../dist/picosm.js'; | ||
|
||
/** | ||
* also see ../index.html | ||
*/ | ||
export class App extends LitElement { | ||
static properties = { | ||
store: {}, | ||
lastProductTitle: {}, | ||
}; | ||
|
||
static styles = [ | ||
css` | ||
:host { | ||
display: flex; | ||
flex-direction: column; | ||
padding: 32px; | ||
background-color: var(--spectrum-global-color-gray-100); | ||
color: var(--spectrum-global-color-gray-900); | ||
} | ||
.cards { | ||
display: flex; | ||
justify-content: space-between; | ||
flex: 1; | ||
} | ||
[slot="footer"] { | ||
display: flex; | ||
gap: 20px; | ||
align-items: center; | ||
} | ||
.cart { | ||
margin-top: 40px; | ||
width: 800px; | ||
} | ||
sp-card { | ||
width: 220px; | ||
min-height: 350px; | ||
} | ||
sp-number-field { | ||
width: 80px; | ||
} | ||
.reaction { | ||
opacity: 0; | ||
margin-top: 20px; | ||
} | ||
@keyframes fadeOut { | ||
0% { | ||
opacity: 1; | ||
} | ||
50% { | ||
opacity: 1; | ||
} | ||
100% { | ||
opacity: 0; | ||
} | ||
} | ||
.fadeOut { | ||
animation-duration: 3s; | ||
animation-name: fadeOut; | ||
} | ||
`, | ||
]; | ||
|
||
constructor() { | ||
super(); | ||
this.store = new StoreObservable(); | ||
this.reactionDisposer = reaction( | ||
this.store, | ||
({ lastProduct }) => [lastProduct?.title], | ||
(title) => { | ||
const reaction = this.shadowRoot.querySelector('.reaction'); | ||
reaction.classList.remove('fadeOut'); | ||
void reaction.offsetWidth; // Trigger reflow to restart the animation | ||
reaction.classList.add('fadeOut'); | ||
this.lastProductTitle = title; | ||
}, | ||
); | ||
} | ||
|
||
disconnectedCallback() { | ||
super.disconnectedCallback(); | ||
this.reactionDisposer?.(); | ||
} | ||
|
||
get cards() { | ||
return this.store.products.map( | ||
(product, index) => html` | ||
<sp-card heading="${product.title}" subheading="$${product.price}"> | ||
<img | ||
slot="preview" | ||
src="https://picsum.photos/id/${(index + 1) * 10}/200/300" | ||
alt="Product Image ${index}" | ||
/> | ||
<div slot="footer"> | ||
${product.quantity === 0 | ||
? html` | ||
<sp-button | ||
variant="accent" | ||
@click="${() => this.store.addToCart(product)}" | ||
>Add</sp-button | ||
> | ||
` | ||
: ''} | ||
${product.quantity > 0 | ||
? html` | ||
<sp-field-label | ||
side-aligned="start" | ||
for="card-quantity-${index}" | ||
>Quantity</sp-field-label | ||
> | ||
<sp-number-field | ||
id="card-quantity-${index}" | ||
value="${product.quantity}" | ||
@change="${(e) => | ||
this.store.updateQuantity(product, e.target.value)}" | ||
></sp-number-field> | ||
` | ||
: ''} | ||
</div> | ||
</sp-card> | ||
`, | ||
); | ||
} | ||
|
||
get cart() { | ||
const cartItems = this.store.products | ||
.filter((p) => p.quantity > 0) | ||
.map( | ||
(p) => html` | ||
<sp-table-row> | ||
<sp-table-cell>${p.title}</sp-table-cell> | ||
<sp-table-cell>${p.quantity}</sp-table-cell> | ||
<sp-table-cell>$${p.total}</sp-table-cell> | ||
</sp-table-row> | ||
`, | ||
); | ||
return html` <p>Cart Total: $${this.store.total}</p> | ||
<sp-table size="m"> | ||
<sp-table-head> | ||
<sp-table-head-cell>Name</sp-table-head-cell> | ||
<sp-table-head-cell>Quantity</sp-table-head-cell> | ||
<sp-table-head-cell>Total</sp-table-head-cell> | ||
</sp-table-head> | ||
<sp-table-body> ${cartItems} </sp-table-body> | ||
</sp-table>`; | ||
} | ||
render() { | ||
return html` | ||
<div class="cards">${this.cards}</div> | ||
<div class="cart"> | ||
${this.cart} | ||
<div class="reaction"> | ||
Last interacted product: | ||
<sp-badge variant="negative">${this.lastProductTitle} </sp-badge> | ||
</div> | ||
</div> | ||
`; | ||
} | ||
} | ||
|
||
customElements.define('pico-demo', makeObserver(App, ['store'])); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import './App.js'; | ||
import { ProductObservable } from './model.js'; | ||
|
||
const products = [ | ||
new ProductObservable('Product A', 10), | ||
new ProductObservable('Product B', 15), | ||
new ProductObservable('Product C', 22), | ||
]; | ||
|
||
const app = document.querySelector('pico-demo'); | ||
app.store.setProducts(products); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { makeObservable } from '../dist/picosm.js'; | ||
|
||
class Product { | ||
title = ''; | ||
price = 0; | ||
quantity = 0; | ||
|
||
constructor(title, price) { | ||
this.title = title; | ||
this.price = price; | ||
} | ||
|
||
get total() { | ||
return this.price * this.quantity; | ||
} | ||
|
||
setQuantity(quantity) { | ||
this.quantity = quantity; | ||
} | ||
} | ||
|
||
class Store { | ||
setProducts(products) { | ||
this.products = products; | ||
} | ||
|
||
addToCart(product) { | ||
this.lastProduct = product; | ||
product.setQuantity(Math.max(product.quantity + 1, 1)); | ||
} | ||
|
||
updateQuantity(product, value) { | ||
this.lastProduct = product; | ||
product.setQuantity(Math.max(value || 0, 0)); | ||
} | ||
|
||
get total() { | ||
return this.products | ||
.filter((p) => p.quantity > 0) | ||
.reduce((total, product) => { | ||
return total + product.total; | ||
}, 0); | ||
} | ||
} | ||
|
||
const ProductObservable = makeObservable(Product, ['setQuantity'], ['total']); | ||
const StoreObservable = makeObservable( | ||
Store, | ||
['setProducts', 'addToCart', 'updateQuantity'], | ||
['total'], | ||
); | ||
|
||
export { Product, ProductObservable, Store, StoreObservable }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import '@spectrum-web-components/badge/sp-badge.js'; | ||
import '@spectrum-web-components/button/sp-button.js'; | ||
import '@spectrum-web-components/card/sp-card.js'; | ||
import '@spectrum-web-components/number-field/sp-number-field.js'; | ||
import '@spectrum-web-components/table/sp-table-body.js'; | ||
import '@spectrum-web-components/table/sp-table-cell.js'; | ||
import '@spectrum-web-components/table/sp-table-checkbox-cell.js'; | ||
import '@spectrum-web-components/table/sp-table-head-cell.js'; | ||
import '@spectrum-web-components/table/sp-table-head.js'; | ||
import '@spectrum-web-components/table/sp-table-row.js'; | ||
import '@spectrum-web-components/table/sp-table.js'; | ||
import '@spectrum-web-components/theme/scale-large.js'; | ||
import '@spectrum-web-components/theme/sp-theme.js'; | ||
import '@spectrum-web-components/theme/theme-dark.js'; |
Oops, something went wrong.