diff --git a/package-lock.json b/package-lock.json index b3898ea828..910f476846 100644 --- a/package-lock.json +++ b/package-lock.json @@ -182,9 +182,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -202,9 +199,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -222,9 +216,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -242,9 +233,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -545,9 +533,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -562,9 +547,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -579,9 +561,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -596,9 +575,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1433,9 +1409,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1453,9 +1426,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1473,9 +1443,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1493,9 +1460,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1513,9 +1477,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1533,9 +1494,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1553,9 +1511,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1573,9 +1528,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1593,9 +1545,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1619,9 +1568,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1645,9 +1591,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1671,9 +1614,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1697,9 +1637,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1723,9 +1660,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1749,9 +1683,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1775,9 +1706,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -2093,6 +2021,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2113,6 +2042,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2133,6 +2063,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2153,6 +2084,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2173,9 +2105,7 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2196,9 +2126,7 @@ "cpu": [ "arm" ], - "libc": [ - "musl" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2219,9 +2147,7 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2242,9 +2168,7 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2265,9 +2189,7 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2288,9 +2210,7 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2311,6 +2231,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2331,6 +2252,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2470,9 +2392,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2490,9 +2409,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2510,9 +2426,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2530,9 +2443,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2550,9 +2460,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2570,9 +2477,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2873,9 +2777,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2893,9 +2794,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2913,9 +2811,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2933,9 +2828,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -5562,9 +5454,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5586,9 +5475,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5610,9 +5496,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5634,9 +5517,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -6891,7 +6771,6 @@ "cpu": [ "arm" ], - "libc": "glibc", "license": "MIT", "optional": true, "os": [ @@ -6908,7 +6787,6 @@ "cpu": [ "arm64" ], - "libc": "glibc", "license": "MIT", "optional": true, "os": [ @@ -6925,7 +6803,6 @@ "cpu": [ "arm" ], - "libc": "musl", "license": "MIT", "optional": true, "os": [ @@ -6942,7 +6819,6 @@ "cpu": [ "arm64" ], - "libc": "musl", "license": "MIT", "optional": true, "os": [ @@ -6959,7 +6835,6 @@ "cpu": [ "riscv64" ], - "libc": "musl", "license": "MIT", "optional": true, "os": [ @@ -6976,7 +6851,6 @@ "cpu": [ "x64" ], - "libc": "musl", "license": "MIT", "optional": true, "os": [ @@ -6993,7 +6867,6 @@ "cpu": [ "riscv64" ], - "libc": "glibc", "license": "MIT", "optional": true, "os": [ @@ -7010,7 +6883,6 @@ "cpu": [ "x64" ], - "libc": "glibc", "license": "MIT", "optional": true, "os": [ @@ -8278,9 +8150,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -8302,9 +8171,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -8326,9 +8192,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -8350,9 +8213,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ diff --git a/samples/inputs/combo/remote-binding/README.md b/samples/inputs/combo/remote-binding/README.md new file mode 100644 index 0000000000..80076743d1 --- /dev/null +++ b/samples/inputs/combo/remote-binding/README.md @@ -0,0 +1,15 @@ +This folder contains a React application with the Combo Remote Binding example. + +- [Run in the samples browser](https://www.infragistics.com/react-demos/inputs/combo/remote-binding) +- [Source](./src/index.tsx) + +## Run locally + +``` +git clone https://github.com/IgniteUI/igniteui-react-examples.git +cd igniteui-react-examples/samples/inputs/combo/remote-binding +npm install +npm start +``` + +Then open http://localhost:4200/ in your browser. diff --git a/samples/inputs/combo/remote-binding/index.html b/samples/inputs/combo/remote-binding/index.html new file mode 100644 index 0000000000..ed6a157174 --- /dev/null +++ b/samples/inputs/combo/remote-binding/index.html @@ -0,0 +1,13 @@ + + + + + Combo Remote Binding + + + + +
+ + + diff --git a/samples/inputs/combo/remote-binding/package.json b/samples/inputs/combo/remote-binding/package.json new file mode 100644 index 0000000000..c546dd50b9 --- /dev/null +++ b/samples/inputs/combo/remote-binding/package.json @@ -0,0 +1,25 @@ +{ + "name": "react-combo-remote-binding", + "description": "This project provides example of Combo Remote Binding using Infragistics React components", + "author": "Infragistics", + "version": "1.0.0", + "private": true, + "scripts": { + "start": "vite --port 4200", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "igniteui-react": "19.9.0", + "igniteui-webcomponents": "~7.4.1", + "react": "^19.3.0", + "react-dom": "^19.3.0" + }, + "devDependencies": { + "@types/react": "^19.3.0", + "@types/react-dom": "^19.3.0", + "@vitejs/plugin-react": "^6.1.1", + "typescript": "^5.9.3", + "vite": "^8.3.1" + } +} diff --git a/samples/inputs/combo/remote-binding/src/ProductsService.ts b/samples/inputs/combo/remote-binding/src/ProductsService.ts new file mode 100644 index 0000000000..480d70629e --- /dev/null +++ b/samples/inputs/combo/remote-binding/src/ProductsService.ts @@ -0,0 +1,59 @@ +export interface Product { + ProductID: number; + ProductName: string; +} + +export interface ProductsPage { + items: Product[]; + /** How many products match the query on the server, across all pages. */ + total: number; +} + +export interface ProductsQuery { + search: string; + skip: number; + top: number; +} + +const PRODUCTS_URL = 'https://services.odata.org/V4/Northwind/Northwind.svc/Products'; + +/** Builds an OData request for one page of products whose name contains `search`. */ +export function buildProductsUrl({ search, skip, top }: ProductsQuery): string { + const params = [ + '$select=ProductID,ProductName', + '$orderby=ProductName', + '$count=true', + `$skip=${skip}`, + `$top=${top}`, + ]; + + if (search) { + // OData escapes a single quote inside a string literal by doubling it. + const term = search.toLowerCase().replace(/'/g, "''"); + params.push(`$filter=contains(tolower(ProductName),'${encodeURIComponent(term)}')`); + } + + return `${PRODUCTS_URL}?${params.join('&')}`; +} + +export async function fetchProducts(url: string, signal: AbortSignal): Promise { + const response = await fetch(url, { signal }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status} ${response.statusText}`); + } + + const json = await response.json(); + return { items: json.value, total: json['@odata.count'] }; +} + +/** Waits `ms`, or rejects as soon as `signal` aborts. */ +export function delay(ms: number, signal: AbortSignal): Promise { + return new Promise((resolve, reject) => { + const timer = setTimeout(resolve, ms); + signal.addEventListener('abort', () => { + clearTimeout(timer); + reject(signal.reason); + }, { once: true }); + }); +} diff --git a/samples/inputs/combo/remote-binding/src/index.css b/samples/inputs/combo/remote-binding/src/index.css new file mode 100644 index 0000000000..92370b9ab1 --- /dev/null +++ b/samples/inputs/combo/remote-binding/src/index.css @@ -0,0 +1,144 @@ +/* shared styles are loaded from: */ +/* https://dl.infragistics.com/x/css/samples/shared.v8.css */ + +.sample { + gap: 24px; + max-width: 760px; +} + +/* Slotted into the combo's dropdown */ + +/* A thin bar at the top of the dropdown, instead of Bootstrap's 1rem track */ +.combo-progress { + --ig-linear-bar-track-height: 4px; + --ig-linear-bar-track-border-radius: 0; +} + +.combo-empty { + padding: 12px 16px; + color: var(--ig-gray-700); +} + +.combo-footer { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + min-height: 40px; + padding: 0 4px 0 16px; + border-top: 1px solid var(--ig-gray-300); +} + +.combo-footer-text { + font-size: 0.8125rem; + color: var(--ig-gray-700); +} + +/* Background operations log */ + +.log { + display: flex; + flex-direction: column; + border: 1px solid var(--ig-gray-300); + border-radius: 6px; + overflow: hidden; +} + +.log-header { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 8px 16px; + padding: 8px 12px; + border-bottom: 1px solid var(--ig-gray-300); + background: var(--ig-gray-50); +} + +/* Two classes, to win over the theme's `.ig-typography h2` */ +.log .log-title { + margin: 0; + font-size: 1rem; + font-weight: 600; + line-height: 1.5; + letter-spacing: normal; +} + +.log-options { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 16px; +} + +.log-entries { + height: 280px; + margin: 0; + padding: 4px 0; + overflow-y: auto; + list-style: none; + font-family: ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', monospace; + font-size: 0.8125rem; + line-height: 1.5; +} + +.log-entry { + --kind-color: var(--ig-gray-700); + + display: grid; + grid-template-columns: auto 5.5rem 1fr; + align-items: baseline; + gap: 12px; + padding: 2px 12px; +} + +.log-entry--event { + --kind-color: var(--ig-secondary-700); +} + +.log-entry--request { + --kind-color: var(--ig-primary-600); +} + +.log-entry--response, +.log-entry--data { + --kind-color: var(--ig-success-700); +} + +.log-entry--abort { + --kind-color: var(--ig-warn-800); +} + +.log-entry--error { + --kind-color: var(--ig-error-600); +} + +.log-entry--error .log-message { + color: var(--ig-error-600); +} + +.log-time { + color: var(--ig-gray-600); +} + +.log-kind { + justify-self: start; + padding: 0 6px; + border-radius: 4px; + font-size: 0.6875rem; + font-weight: 600; + letter-spacing: 0.04em; + text-transform: uppercase; + color: var(--kind-color); + background: color-mix(in srgb, var(--kind-color) 12%, transparent); +} + +.log-message { + min-width: 0; +} + +.log-detail { + display: block; + color: var(--ig-gray-600); + word-break: break-all; +} diff --git a/samples/inputs/combo/remote-binding/src/index.tsx b/samples/inputs/combo/remote-binding/src/index.tsx new file mode 100644 index 0000000000..f49c7b40ff --- /dev/null +++ b/samples/inputs/combo/remote-binding/src/index.tsx @@ -0,0 +1,290 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import ReactDOM from 'react-dom/client'; +import { + IgrButton, + IgrCombo, + IgrComboChangeEventArgs, + IgrLinearProgress, + IgrSwitch, +} from 'igniteui-react'; +import 'igniteui-webcomponents/themes/light/bootstrap.css'; +import './index.css'; +import { buildProductsUrl, delay, fetchProducts, Product, ProductsQuery } from './ProductsService'; + +const PAGE_SIZE = 20; +const DEBOUNCE_MS = 300; +const SLOW_NETWORK_MS = 1500; +const MAX_LOG_ENTRIES = 200; + +type Status = 'idle' | 'loading' | 'error'; + +type LogKind = 'event' | 'input' | 'info' | 'request' | 'response' | 'data' | 'abort' | 'error'; + +interface LogEntry { + id: number; + time: Date; + kind: LogKind; + message: string; + /** Secondary line, e.g. the request URL. */ + detail?: string; +} + +let nextLogId = 0; + +const createEntry = (kind: LogKind, message: string, detail?: string): LogEntry => ({ + id: nextLogId++, + time: new Date(), + kind, + message, + detail, +}); + +const formatTime = (time: Date) => + `${time.toLocaleTimeString('en-GB', { hour12: false })}.${String(time.getMilliseconds()).padStart(3, '0')}`; + +export default function ComboRemoteBinding() { + const comboRef = useRef(null); + const logRef = useRef(null); + + // What the combo renders. The remote results, plus any selected products they don't contain. + const [data, setData] = useState([]); + const [search, setSearch] = useState(''); + const [loaded, setLoaded] = useState(0); + const [total, setTotal] = useState(null); + const [status, setStatus] = useState('idle'); + const [slowNetwork, setSlowNetwork] = useState(false); + const [failRequests, setFailRequests] = useState(false); + const [entries, setEntries] = useState(() => [ + createEntry('info', 'Ready. Open the combo to load the first page of products.'), + ]); + + // Read inside async callbacks, so they live in refs rather than state. + const results = useRef([]); + const selected = useRef(new Map()); + const appliedSearch = useRef(''); + const lastQuery = useRef(null); + const inFlight = useRef(null); + const debounceTimer = useRef(undefined); + const simulation = useRef({ slowNetwork: false, failRequests: false }); + + const log = useCallback((kind: LogKind, message: string, detail?: string) => { + setEntries(prev => [...prev, createEntry(kind, message, detail)].slice(-MAX_LOG_ENTRIES)); + }, []); + + const load = useCallback(async (query: ProductsQuery) => { + if (inFlight.current) { + inFlight.current.abort(); + log('abort', 'Cancelled the request in flight: its results would be stale'); + } + + const controller = new AbortController(); + inFlight.current = controller; + lastQuery.current = query; + + const url = buildProductsUrl(query); + const page = query.skip / query.top + 1; + log('request', `GET page ${page} of products${query.search ? ` matching "${query.search}"` : ''}`, decodeURIComponent(url)); + setStatus('loading'); + const started = performance.now(); + const elapsed = () => Math.round(performance.now() - started); + + try { + if (simulation.current.slowNetwork) { + log('info', `Slow network: holding the request for ${SLOW_NETWORK_MS / 1000} s`); + await delay(SLOW_NETWORK_MS, controller.signal); + } + if (simulation.current.failRequests) { + throw new Error('Simulated failure (HTTP 500 Internal Server Error)'); + } + + const result = await fetchProducts(url, controller.signal); + const rows = query.skip === 0 ? result.items : [...results.current, ...result.items]; + results.current = rows; + log('response', `200 OK in ${elapsed()} ms: ${result.items.length} products, ${rows.length} of ${result.total} loaded`); + + // A selected product missing from the new results stays in the data: + // the combo drops a selected value it can't find in its data. + const ids = new Set(rows.map(p => p.ProductID)); + const kept = [...selected.current.values()].filter(p => !ids.has(p.ProductID)); + setData([...kept, ...rows]); + setLoaded(rows.length); + setTotal(result.total); + setStatus('idle'); + log('data', `Bound ${rows.length + kept.length} items to the combo` + + (kept.length ? `, ${kept.length} of them selected earlier and kept so the selection survives` : '')); + } catch (error) { + // The request that aborted this one has already logged it. + if (controller.signal.aborted) return; + setStatus('error'); + log('error', `Request failed after ${elapsed()} ms: ${(error as Error).message}`); + } finally { + if (inFlight.current === controller) { + inFlight.current = null; + } + } + }, [log]); + + // The combo has no event of its own for its search input, but the input's + // igcInput event bubbles out of the combo's shadow DOM. + useEffect(() => { + const combo = comboRef.current; + if (!combo) return; + + const handleSearchInput = (event: Event) => { + const text = (event as CustomEvent).detail; + const term = text.trim(); + log('input', `Search input: "${text}"`); + + window.clearTimeout(debounceTimer.current); + debounceTimer.current = window.setTimeout(() => { + if (term === appliedSearch.current) { + log('info', `Typing paused, and the results for "${term}" are already loaded: no request`); + return; + } + log('info', `Typing paused for ${DEBOUNCE_MS} ms: searching on the server`); + appliedSearch.current = term; + setSearch(term); + void load({ search: term, skip: 0, top: PAGE_SIZE }); + }, DEBOUNCE_MS); + }; + + combo.addEventListener('igcInput', handleSearchInput); + return () => combo.removeEventListener('igcInput', handleSearchInput); + }, [load, log]); + + useEffect(() => () => { + window.clearTimeout(debounceTimer.current); + inFlight.current?.abort(); + }, []); + + // Keep the newest entry in view. + useEffect(() => { + const list = logRef.current; + if (list) { + list.scrollTop = list.scrollHeight; + } + }, [entries]); + + const handleOpening = () => { + log('event', 'Dropdown opening'); + // Nothing is loaded until the first open, and a failed first load retries on the next one. + if (total === null && !inFlight.current) { + log('info', 'Nothing loaded yet: requesting the first page'); + void load({ search: appliedSearch.current, skip: 0, top: PAGE_SIZE }); + } + }; + + const handleChange = (event: IgrComboChangeEventArgs) => { + const { newValue, type } = event.detail; + const items = event.detail.items as Product[]; + + if (newValue.length === 0) { + selected.current.clear(); + log('event', 'Selection cleared'); + return; + } + + for (const item of items) { + if (type === 'deselection') { + selected.current.delete(item.ProductID); + } else { + selected.current.set(item.ProductID, item); + } + } + const names = items.map(p => p.ProductName).join(', '); + log('event', `${type === 'deselection' ? 'Deselected' : 'Selected'} ${names} (${newValue.length} selected)`); + }; + + const loadMore = () => { + log('event', 'Load more clicked'); + void load({ search: appliedSearch.current, skip: results.current.length, top: PAGE_SIZE }); + }; + + const retry = () => { + log('event', 'Retry clicked'); + if (lastQuery.current) { + void load(lastQuery.current); + } + }; + + const emptyMessage = + status === 'loading' ? 'Loading products…' : + status === 'error' ? 'Couldn’t load the products.' : + total === 0 ? `No products match "${search}".` : + 'No matching products.'; + + return ( +
+ log('event', 'Dropdown closed')} + onChange={handleChange}> + {status === 'loading' && ( +
+ +
+ )} +
{emptyMessage}
+ {(total !== null || status === 'error') && ( +
+ + {status === 'loading' ? 'Loading…' : + status === 'error' ? 'The request failed.' : + `${loaded} of ${total} loaded`} + + {status === 'error' && ( + Retry + )} + {status === 'idle' && total !== null && loaded < total && ( + Load more + )} +
+ )} +
+ +
+
+

Background operations

+
+ { + simulation.current.slowNetwork = e.detail.checked; + setSlowNetwork(e.detail.checked); + }}> + Slow network + + { + simulation.current.failRequests = e.detail.checked; + setFailRequests(e.detail.checked); + }}> + Fail requests + + setEntries([])}>Clear +
+
+
    + {entries.map(entry => ( +
  1. + + {entry.kind} + + {entry.message} + {entry.detail && {entry.detail}} + +
  2. + ))} +
+
+
+ ); +} + +// rendering above component in the React DOM +const root = ReactDOM.createRoot(document.getElementById('root')!); +root.render(); diff --git a/samples/inputs/combo/remote-binding/tsconfig.json b/samples/inputs/combo/remote-binding/tsconfig.json new file mode 100644 index 0000000000..e27a22be9b --- /dev/null +++ b/samples/inputs/combo/remote-binding/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "es2020", + "lib": [ + "es2020", + "dom", + "dom.iterable" + ], + "module": "esnext", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "types": [ + "vite/client" + ], + "strict": true, + "resolveJsonModule": true, + "isolatedModules": true, + "skipLibCheck": true, + "noEmit": true + }, + "include": [ + "src" + ] +} diff --git a/samples/inputs/combo/remote-binding/vite.config.js b/samples/inputs/combo/remote-binding/vite.config.js new file mode 100644 index 0000000000..4fc593892f --- /dev/null +++ b/samples/inputs/combo/remote-binding/vite.config.js @@ -0,0 +1,12 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + build: { + outDir: 'build' + }, + server: { + open: false + }, +});