@nscodecom/loso-pos-elements-v2
v1.0.3
Published
Drop-in <loso-pos-panel> for the Loso POS loyalty API — Angular Elements build. Same contract as @nscodecom/loso-pos-elements, self-contained (Angular runtime included).
Maintainers
Readme
@nscodecom/loso-pos-elements-v2
Drop-in <loso-pos-panel> for the Loso POS loyalty API, built with Angular Elements. Loading
the bundle registers the custom element; it is self-contained (the Angular runtime is included,
zoneless — no zone.js), ~40 kB gzipped.
Which package? This is the Angular Elements build. For a smaller, zero-dependency drop-in with the same
<loso-pos-panel>contract, use@nscodecom/loso-pos-elements(~15 kB). Both register the same tag with the same attributes and events — pick one; don't load both on the same page.
Install
npm install @nscodecom/loso-pos-elements-v2…or load the bundle directly, no build step:
<script type="module" src="https://unpkg.com/@nscodecom/loso-pos-elements-v2"></script>The element
One tag runs the whole loyalty side of a sale — resolve → quote → redeem → commit → refund — and emits a DOM event at each step so your till can mirror the numbers into its own totals.
Attributes
| Attribute | Required | Notes |
|---|---|---|
| base-url | yes¹ | Your proxy — the /api/pos/v1 prefix is added for you. |
| pos-transaction-id | to commit | Your till's sale id; never generated for you. |
| subtotal | yes | Pre-discount basket total, > 0. |
| currency | yes | Must match the merchant's configured currency. |
| payment-method | no | Defaults to other. |
| timeout-ms | no | Per-request timeout. Default 10000. |
¹ Unless you assign a configured client (see Advanced).
Events — all bubble and are composed, so you can listen on an ancestor:
| Event | detail |
|---|---|
| loso-customer-resolved | { customer } |
| loso-quoted | { quote } |
| loso-discount-changed | { discount, percent } |
| loso-committed | { commit } |
| loso-refunded | { refund } |
| loso-error | { error } |
Proxy-only by design. base-url points at your backend, which holds the merchant's
pos_live_… key and forwards to Loso. No key ever touches the page — the element exposes no
attribute that accepts one. Never block a sale on loyalty: failures arrive as loso-error and never
throw; sell at full price if loyalty is unreachable.
Framework examples
Plain HTML / ASP.NET Razor / WordPress
<script type="module" src="https://unpkg.com/@nscodecom/loso-pos-elements-v2"></script>
<loso-pos-panel
base-url="https://till.vendor.example/loyalty"
pos-transaction-id="POS-2026-000481"
subtotal="42.00"
currency="BAM"
payment-method="card"></loso-pos-panel>
<script>
const panel = document.querySelector('loso-pos-panel');
panel.addEventListener('loso-committed', (e) => {
console.log('Committed:', e.detail.commit.loyaltyReference);
});
</script>React
import { useEffect, useRef } from 'react';
import '@nscodecom/loso-pos-elements-v2/define';
export function Loyalty({ subtotal }: { subtotal: number }) {
const ref = useRef<HTMLElement>(null);
useEffect(() => {
const el = ref.current!;
const onCommitted = (e: Event) => console.log((e as CustomEvent).detail.commit);
el.addEventListener('loso-committed', onCommitted);
return () => el.removeEventListener('loso-committed', onCommitted);
}, []);
return (
<loso-pos-panel
ref={ref}
base-url="https://till.vendor.example/loyalty"
pos-transaction-id="POS-1"
subtotal={subtotal.toFixed(2)}
currency="BAM"
/>
);
}TypeScript JSX needs the tag declared once:
declare namespace JSX {
interface IntrinsicElements {
'loso-pos-panel': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> &
{ 'base-url'?: string; 'pos-transaction-id'?: string; subtotal?: string; currency?: string };
}
}Vue 3
<script setup>
import '@nscodecom/loso-pos-elements-v2/define';
const onCommitted = (e) => console.log(e.detail.commit);
</script>
<template>
<loso-pos-panel
base-url="https://till.vendor.example/loyalty"
pos-transaction-id="POS-1"
:subtotal="'42.00'"
currency="BAM"
@loso-committed="onCommitted" />
</template>Tell Vue the tag is a custom element (once), e.g. in vite.config.ts:
vue({ template: { compilerOptions: { isCustomElement: (t) => t === 'loso-pos-panel' } } })Angular
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import '@nscodecom/loso-pos-elements-v2/define';
@Component({
selector: 'app-checkout',
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA], // it's a custom element, unknown to Angular
template: `
<loso-pos-panel
attr.base-url="https://till.vendor.example/loyalty"
[attr.pos-transaction-id]="txId"
[attr.subtotal]="subtotal"
attr.currency="BAM"
(loso-committed)="onCommitted($event)"></loso-pos-panel>
`,
})
export class CheckoutComponent {
txId = 'POS-1';
subtotal = '42.00';
onCommitted(e: Event) {
console.log((e as CustomEvent).detail.commit);
}
}Svelte
<script>
import '@nscodecom/loso-pos-elements-v2/define';
const onCommitted = (e) => console.log(e.detail.commit);
</script>
<loso-pos-panel
base-url="https://till.vendor.example/loyalty"
pos-transaction-id="POS-1"
subtotal="42.00"
currency="BAM"
on:loso-committed={onCommitted} />Styling
Styles are isolated in a shadow root, so a merchant's CSS can't reach in and yours can't leak out. Theme the panel with CSS custom properties — they pierce the shadow boundary:
loso-pos-panel {
--loso-accent: #0b5cff;
--loso-accent-fg: #ffffff;
--loso-radius: 4px;
--loso-font: "Inter", system-ui, sans-serif;
}| Property | Default | Controls |
|---|---|---|
| --loso-accent | #2f6f4f | Primary / action colour (buttons, slider, focus ring) |
| --loso-accent-fg | #ffffff | Text on the accent |
| --loso-font | system UI stack | Font family |
| --loso-radius | 10px | Corner radius |
| --loso-gap | 12px | Spacing between blocks |
| --loso-fg | currentColor | Body text (inherits the host page) |
| --loso-muted | 60% of --loso-fg | Secondary text |
| --loso-border | 18% of --loso-fg | Card and control borders |
| --loso-surface | transparent | Panel background |
| --loso-danger | #b3261e | Error state |
Because --loso-fg, --loso-muted, and --loso-border derive from currentColor, the panel picks
up the host page's text colour by default — set color on an ancestor and it blends in.
Advanced: custom transport
For full control — mTLS, request logging, or a native host that legitimately holds a key — build a
LosoPosClient yourself and assign it to the element's client property:
import { LosoPosClient } from '@nscodecom/loso-pos-sdk';
const panel = document.querySelector('loso-pos-panel');
panel.client = new LosoPosClient({
baseUrl: 'https://till.vendor.example/loyalty',
auth: 'proxy',
fetch: myInstrumentedFetch,
});License
MIT
