@hrforte/react-pivottable 2.1.2 → 2.2.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/README.md CHANGED
@@ -122,7 +122,7 @@ but the main thread is used. Verify with `workerDebug`:
122
122
  - `workerUrl` (`string`, default `'pivot-worker.js'`): URL of the worker bundle
123
123
  (see above).
124
124
  - `workerDebug` (`bool`, default `false`): prints the whole worker pipeline
125
- (worker creation, data staging, progress, compute, result, errors and the
125
+ (worker creation, data staging, compute, result, errors and the
126
126
  fallback to the main thread) to the browser console with a
127
127
  `[pivottable worker]` prefix. Logs emitted inside the worker thread are
128
128
  forwarded to the page console as well. Can also be enabled globally with
@@ -130,5 +130,7 @@ but the main thread is used. Verify with `workerDebug`:
130
130
  - `isShowProcessing` (`bool`, default `false`): testing/debugging hook. When
131
131
  `true` the loading overlay is forced on top of the table even if nothing is
132
132
  being computed, so the `PivotLoadingOverlay` can be inspected without
133
- generating a large dataset. A sample progress value is supplied so the
134
- progress bar and percentage are rendered as well.
133
+ generating a large dataset. The overlay shows only a spinner and a label;
134
+ during real computations it is turned off only after the worker has posted
135
+ its result event **and** PivotTableUI has rendered the new table (the
136
+ removal happens before the browser paints, so no empty frame is shown).
@@ -20,7 +20,7 @@ var DEFAULT_WORKER_URL = exports.DEFAULT_WORKER_URL = 'pivot-worker.js'; /* esli
20
20
  *
21
21
  * Responsibilities:
22
22
  * - lazily create a single shared Worker (at `workerUrl`, overrideable),
23
- * - route progress/result/error messages to the matching request,
23
+ * - route result/error messages to the matching request,
24
24
  * - transparently fall back to a synchronous main-thread computation when
25
25
  * workers are unavailable or the worker bundle fails to load.
26
26
  *
@@ -126,11 +126,9 @@ var handleWorkerMessage = function handleWorkerMessage(event) {
126
126
  return;
127
127
  }
128
128
  if (message.type === 'pivot:progress') {
129
- var percent = message.total > 0 ? Math.round(message.processed / message.total * 100) : null;
130
- log('#' + message.id + ' progress ' + message.phase + ': ' + message.processed + (message.total ? '/' + message.total : '') + (percent === null ? '' : ' (' + percent + '%)'));
131
- if (entry.onProgress) {
132
- entry.onProgress(message.processed, message.total, message.phase);
133
- }
129
+ // Progress reporting was removed: the UI only shows a loading overlay
130
+ // until `pivot:result` arrives. Ignore messages from older worker
131
+ // bundles that still emit progress so the request is not corrupted.
134
132
  return;
135
133
  }
136
134
  clearTimeout(entry.timer);
@@ -193,7 +191,7 @@ var ensureWorker = function ensureWorker() {
193
191
  * worker reuse the materialized dataset across consecutive computations and
194
192
  * lets the client skip re-staging identical data.
195
193
  */
196
- function computePivotInWorker(data, dataKey, config, onProgress) {
194
+ function computePivotInWorker(data, dataKey, config) {
197
195
  var worker = ensureWorker();
198
196
  if (!worker) {
199
197
  log('worker unavailable -> synchronous fallback');
@@ -209,7 +207,6 @@ function computePivotInWorker(data, dataKey, config, onProgress) {
209
207
  pendingRequests.set(id, {
210
208
  resolve: resolve,
211
209
  reject: reject,
212
- onProgress: onProgress,
213
210
  timer: timer,
214
211
  startedAt: Date.now()
215
212
  });
@@ -228,16 +225,14 @@ function computePivotInWorker(data, dataKey, config, onProgress) {
228
225
  * automatic fallback whenever the worker path is unavailable. Deferred by
229
226
  * one tick so a loading indicator can paint before the blocking work starts.
230
227
  */
231
- function computePivotSynchronously(data, config, onProgress) {
228
+ function computePivotSynchronously(data, config) {
232
229
  return new Promise(function (resolve, reject) {
233
230
  setTimeout(function () {
234
231
  var startedAt = Date.now();
235
232
  log('running synchronous computation on the main thread...');
236
233
  try {
237
- var materialized = (0, _pivotCompute.materializeData)(data, {}, function (processed, total) {
238
- onProgress(processed, total, 'materialize');
239
- });
240
- var result = (0, _pivotCompute.computePivotState)(materialized, config, onProgress);
234
+ var materialized = (0, _pivotCompute.materializeData)(data, {});
235
+ var result = (0, _pivotCompute.computePivotState)(materialized, config);
241
236
  log('synchronous computation finished in ' + (Date.now() - startedAt) + 'ms', {
242
237
  totalRecords: result.totalRecords,
243
238
  rowKeys: result.rowKeys.length,
@@ -257,10 +252,10 @@ function computePivotSynchronously(data, config, onProgress) {
257
252
  * produces the exact same serialized shape, so callers render identically
258
253
  * either way.
259
254
  */
260
- function computePivotWithFallback(data, dataKey, config, onProgress) {
261
- return computePivotInWorker(data, dataKey, config, onProgress).catch(function (error) {
255
+ function computePivotWithFallback(data, dataKey, config) {
256
+ return computePivotInWorker(data, dataKey, config).catch(function (error) {
262
257
  log('worker request failed, falling back to the main thread:', error && error.message || error);
263
- return computePivotSynchronously(data, config, onProgress);
258
+ return computePivotSynchronously(data, config);
264
259
  });
265
260
  }
266
261
  //# sourceMappingURL=PivotDataWorker.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/PivotDataWorker.js"],"names":["setWorkerDebug","configureWorkerUrl","computePivotInWorker","computePivotSynchronously","computePivotWithFallback","DEFAULT_WORKER_URL","REQUEST_TIMEOUT_MS","workerUrl","sharedWorker","sharedWorkerBroken","hasResultEver","nextRequestId","lastStagedDataKey","workerDebug","pendingRequests","Map","enabled","next","Boolean","wasEnabled","log","postMessage","type","isDebugEnabled","window","__PVT_WORKER_DEBUG__","args","describeConfig","config","rows","join","cols","aggregations","length","countOf","Array","isArray","value","undefined","failAllPending","forEach","clearTimeout","entry","timer","reject","error","clear","url","Error","terminate","handleWorkerMessage","message","event","data","detail","get","id","percent","total","Math","round","processed","phase","onProgress","delete","result","Date","now","startedAt","totalRecords","rowKeys","colKeys","resolve","ensureWorker","Worker","onmessage","onerror","dataKey","worker","Promise","setTimeout","set","mustStage","materialized","catch"],"mappings":"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","file":"PivotDataWorker.js","sourcesContent":["/* eslint-disable valid-jsdoc */\n/* eslint-disable no-console */\n/* eslint-disable no-undefined */\n/*\n * Main-thread client for the pivot Web Worker (src/pivotWorker.js).\n *\n * Responsibilities:\n * - lazily create a single shared Worker (at `workerUrl`, overrideable),\n * - route progress/result/error messages to the matching request,\n * - transparently fall back to a synchronous main-thread computation when\n * workers are unavailable or the worker bundle fails to load.\n *\n * Debug logging is off by default. Turn it on with the `workerDebug` prop of\n * PivotTableUI (or by setting `window.__PVT_WORKER_DEBUG__ = true` before\n * rendering) to trace worker creation, staging, computation, progress,\n * results, errors and fallbacks in the browser console.\n */\nimport {materializeData, computePivotState} from './pivotCompute';\n\nexport const DEFAULT_WORKER_URL = 'pivot-worker.js';\n\nconst REQUEST_TIMEOUT_MS = 300000;\n\nlet workerUrl = DEFAULT_WORKER_URL;\nlet sharedWorker = null;\n// The worker is marked broken only if it fails before ever producing a\n// result (e.g. a 404 worker script); runtime hiccups later on just reject\n// the affected request and fall back once.\nlet sharedWorkerBroken = false;\nlet hasResultEver = false;\nlet nextRequestId = 1;\nlet lastStagedDataKey = null;\nlet workerDebug = false;\nconst pendingRequests = new Map();\n\nexport function setWorkerDebug(enabled) {\n const next = Boolean(enabled);\n const wasEnabled = workerDebug;\n if (next === wasEnabled) {\n return;\n }\n if (next) {\n workerDebug = true;\n log('debug logging enabled');\n } else {\n // Log while logging is still active, so the \"disabled\" notice is shown.\n log('debug logging disabled');\n workerDebug = false;\n }\n if (sharedWorker) {\n sharedWorker.postMessage({type: 'pivot:debug', enabled: workerDebug});\n }\n}\n\nconst isDebugEnabled = () =>\n workerDebug ||\n (typeof window !== 'undefined' && window.__PVT_WORKER_DEBUG__ === true);\n\nfunction log(...args) {\n if (!isDebugEnabled()) {\n return;\n }\n console.log(\n '%c[pivottable worker]',\n 'color:#ff6600;font-weight:bold',\n ...args\n );\n}\n\nconst describeConfig = config =>\n `rows=[${(config.rows || []).join(', ')}] cols=[${(config.cols || []).join(\n ', '\n )}] aggregations=${(config.aggregations || []).length}`;\n\n// Length of an optional array field, used for defensive debug logging.\nconst countOf = value => (Array.isArray(value) ? value.length : undefined);\n\nconst failAllPending = error => {\n pendingRequests.forEach(entry => {\n clearTimeout(entry.timer);\n entry.reject(error);\n });\n pendingRequests.clear();\n};\n\nexport function configureWorkerUrl(url) {\n if (url && url !== workerUrl) {\n workerUrl = url;\n if (sharedWorker) {\n // Reject in-flight requests first so callers fall back to the\n // synchronous path instead of waiting for the request timeout.\n failAllPending(new Error('Pivot worker was reconfigured'));\n sharedWorker.terminate();\n sharedWorker = null;\n sharedWorkerBroken = false;\n hasResultEver = false;\n lastStagedDataKey = null;\n }\n }\n}\n\nconst handleWorkerMessage = event => {\n const message = event.data || {};\n if (message.type === 'pivot:log') {\n log('(in worker)', message.message, message.detail || '');\n return;\n }\n const entry = pendingRequests.get(message.id);\n if (!entry) {\n return;\n }\n if (message.type === 'pivot:progress') {\n const percent =\n message.total > 0\n ? Math.round((message.processed / message.total) * 100)\n : null;\n log(\n `#${message.id} progress ${message.phase}: ${message.processed}` +\n (message.total ? `/${message.total}` : '') +\n (percent === null ? '' : ` (${percent}%)`)\n );\n if (entry.onProgress) {\n entry.onProgress(message.processed, message.total, message.phase);\n }\n return;\n }\n clearTimeout(entry.timer);\n pendingRequests.delete(message.id);\n if (message.type === 'pivot:result') {\n hasResultEver = true;\n const result = message.result;\n log(`#${message.id} result received in ${Date.now() - entry.startedAt}ms`, {\n totalRecords: result && result.totalRecords,\n rowKeys: countOf(result && result.rowKeys),\n colKeys: countOf(result && result.colKeys),\n aggregations: countOf(result && result.aggregations),\n });\n entry.resolve(message.result);\n } else if (message.type === 'pivot:error') {\n log(`#${message.id} worker reported an error:`, message.message);\n entry.reject(new Error(message.message || 'Pivot worker error'));\n }\n};\n\nconst ensureWorker = () => {\n if (sharedWorkerBroken) {\n return null;\n }\n\n if (typeof Worker === 'undefined') {\n log('no Worker support in this environment');\n return null;\n }\n\n if (!sharedWorker) {\n try {\n sharedWorker = new Worker(workerUrl);\n log('worker created:', workerUrl);\n } catch (error) {\n sharedWorkerBroken = true;\n log('worker creation failed:', error && error.message);\n return null;\n }\n sharedWorker.onmessage = handleWorkerMessage;\n sharedWorker.onerror = event => {\n log(\n 'worker error event (script failed to load / runtime error):',\n (event && event.message) || ''\n );\n failAllPending(new Error('Pivot worker failed to load'));\n if (!hasResultEver) {\n sharedWorkerBroken = true;\n log('worker marked unusable -> will use the synchronous fallback');\n }\n };\n if (isDebugEnabled()) {\n sharedWorker.postMessage({type: 'pivot:debug', enabled: true});\n }\n }\n return sharedWorker;\n};\n\n/*\n * Runs a full computation inside the shared worker.\n *\n * `dataKey` must change whenever the underlying dataset changes. It lets the\n * worker reuse the materialized dataset across consecutive computations and\n * lets the client skip re-staging identical data.\n */\nexport function computePivotInWorker(data, dataKey, config, onProgress) {\n const worker = ensureWorker();\n if (!worker) {\n log('worker unavailable -> synchronous fallback');\n return Promise.reject(new Error('Web worker unavailable'));\n }\n const id = nextRequestId++;\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n pendingRequests.delete(id);\n log(`#${id} timed out after ${REQUEST_TIMEOUT_MS}ms`);\n reject(new Error('Pivot worker timed out'));\n }, REQUEST_TIMEOUT_MS);\n pendingRequests.set(id, {\n resolve,\n reject,\n onProgress,\n timer,\n startedAt: Date.now(),\n });\n const mustStage = lastStagedDataKey !== dataKey;\n log(\n `#${id} dispatching to worker (dataKey=${dataKey}${\n mustStage\n ? ', staging ' + (data || []).length + ' records'\n : ', cached dataset'\n }, ${describeConfig(config)})`\n );\n if (mustStage) {\n worker.postMessage({type: 'pivot:setData', id, dataKey, data});\n lastStagedDataKey = dataKey;\n }\n worker.postMessage({type: 'pivot:compute', id, dataKey, config});\n });\n}\n\n/*\n * Same contract as the worker path, executed on the main thread. Used as the\n * automatic fallback whenever the worker path is unavailable. Deferred by\n * one tick so a loading indicator can paint before the blocking work starts.\n */\nexport function computePivotSynchronously(data, config, onProgress) {\n return new Promise((resolve, reject) => {\n setTimeout(() => {\n const startedAt = Date.now();\n log('running synchronous computation on the main thread...');\n try {\n const materialized = materializeData(data, {}, (processed, total) => {\n onProgress(processed, total, 'materialize');\n });\n const result = computePivotState(materialized, config, onProgress);\n log(`synchronous computation finished in ${Date.now() - startedAt}ms`, {\n totalRecords: result.totalRecords,\n rowKeys: result.rowKeys.length,\n colKeys: result.colKeys.length,\n });\n resolve(result);\n } catch (error) {\n log('synchronous computation failed:', error && error.message);\n reject(error);\n }\n }, 0);\n });\n}\n\n/*\n * Worker-first entry point with automatic synchronous fallback. The fallback\n * produces the exact same serialized shape, so callers render identically\n * either way.\n */\nexport function computePivotWithFallback(data, dataKey, config, onProgress) {\n return computePivotInWorker(data, dataKey, config, onProgress).catch(\n error => {\n log(\n 'worker request failed, falling back to the main thread:',\n (error && error.message) || error\n );\n return computePivotSynchronously(data, config, onProgress);\n }\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/PivotDataWorker.js"],"names":["setWorkerDebug","configureWorkerUrl","computePivotInWorker","computePivotSynchronously","computePivotWithFallback","DEFAULT_WORKER_URL","REQUEST_TIMEOUT_MS","workerUrl","sharedWorker","sharedWorkerBroken","hasResultEver","nextRequestId","lastStagedDataKey","workerDebug","pendingRequests","Map","enabled","next","Boolean","wasEnabled","log","postMessage","type","isDebugEnabled","window","__PVT_WORKER_DEBUG__","args","describeConfig","config","rows","join","cols","aggregations","length","countOf","Array","isArray","value","undefined","failAllPending","forEach","clearTimeout","entry","timer","reject","error","clear","url","Error","terminate","handleWorkerMessage","message","event","data","detail","get","id","delete","result","Date","now","startedAt","totalRecords","rowKeys","colKeys","resolve","ensureWorker","Worker","onmessage","onerror","dataKey","worker","Promise","setTimeout","set","mustStage","materialized","catch"],"mappings":"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","file":"PivotDataWorker.js","sourcesContent":["/* eslint-disable valid-jsdoc */\n/* eslint-disable no-console */\n/* eslint-disable no-undefined */\n/*\n * Main-thread client for the pivot Web Worker (src/pivotWorker.js).\n *\n * Responsibilities:\n * - lazily create a single shared Worker (at `workerUrl`, overrideable),\n * - route result/error messages to the matching request,\n * - transparently fall back to a synchronous main-thread computation when\n * workers are unavailable or the worker bundle fails to load.\n *\n * Debug logging is off by default. Turn it on with the `workerDebug` prop of\n * PivotTableUI (or by setting `window.__PVT_WORKER_DEBUG__ = true` before\n * rendering) to trace worker creation, staging, computation, progress,\n * results, errors and fallbacks in the browser console.\n */\nimport {materializeData, computePivotState} from './pivotCompute';\n\nexport const DEFAULT_WORKER_URL = 'pivot-worker.js';\n\nconst REQUEST_TIMEOUT_MS = 300000;\n\nlet workerUrl = DEFAULT_WORKER_URL;\nlet sharedWorker = null;\n// The worker is marked broken only if it fails before ever producing a\n// result (e.g. a 404 worker script); runtime hiccups later on just reject\n// the affected request and fall back once.\nlet sharedWorkerBroken = false;\nlet hasResultEver = false;\nlet nextRequestId = 1;\nlet lastStagedDataKey = null;\nlet workerDebug = false;\nconst pendingRequests = new Map();\n\nexport function setWorkerDebug(enabled) {\n const next = Boolean(enabled);\n const wasEnabled = workerDebug;\n if (next === wasEnabled) {\n return;\n }\n if (next) {\n workerDebug = true;\n log('debug logging enabled');\n } else {\n // Log while logging is still active, so the \"disabled\" notice is shown.\n log('debug logging disabled');\n workerDebug = false;\n }\n if (sharedWorker) {\n sharedWorker.postMessage({type: 'pivot:debug', enabled: workerDebug});\n }\n}\n\nconst isDebugEnabled = () =>\n workerDebug ||\n (typeof window !== 'undefined' && window.__PVT_WORKER_DEBUG__ === true);\n\nfunction log(...args) {\n if (!isDebugEnabled()) {\n return;\n }\n console.log(\n '%c[pivottable worker]',\n 'color:#ff6600;font-weight:bold',\n ...args\n );\n}\n\nconst describeConfig = config =>\n `rows=[${(config.rows || []).join(', ')}] cols=[${(config.cols || []).join(\n ', '\n )}] aggregations=${(config.aggregations || []).length}`;\n\n// Length of an optional array field, used for defensive debug logging.\nconst countOf = value => (Array.isArray(value) ? value.length : undefined);\n\nconst failAllPending = error => {\n pendingRequests.forEach(entry => {\n clearTimeout(entry.timer);\n entry.reject(error);\n });\n pendingRequests.clear();\n};\n\nexport function configureWorkerUrl(url) {\n if (url && url !== workerUrl) {\n workerUrl = url;\n if (sharedWorker) {\n // Reject in-flight requests first so callers fall back to the\n // synchronous path instead of waiting for the request timeout.\n failAllPending(new Error('Pivot worker was reconfigured'));\n sharedWorker.terminate();\n sharedWorker = null;\n sharedWorkerBroken = false;\n hasResultEver = false;\n lastStagedDataKey = null;\n }\n }\n}\n\nconst handleWorkerMessage = event => {\n const message = event.data || {};\n if (message.type === 'pivot:log') {\n log('(in worker)', message.message, message.detail || '');\n return;\n }\n const entry = pendingRequests.get(message.id);\n if (!entry) {\n return;\n }\n if (message.type === 'pivot:progress') {\n // Progress reporting was removed: the UI only shows a loading overlay\n // until `pivot:result` arrives. Ignore messages from older worker\n // bundles that still emit progress so the request is not corrupted.\n return;\n }\n clearTimeout(entry.timer);\n pendingRequests.delete(message.id);\n if (message.type === 'pivot:result') {\n hasResultEver = true;\n const result = message.result;\n log(`#${message.id} result received in ${Date.now() - entry.startedAt}ms`, {\n totalRecords: result && result.totalRecords,\n rowKeys: countOf(result && result.rowKeys),\n colKeys: countOf(result && result.colKeys),\n aggregations: countOf(result && result.aggregations),\n });\n entry.resolve(message.result);\n } else if (message.type === 'pivot:error') {\n log(`#${message.id} worker reported an error:`, message.message);\n entry.reject(new Error(message.message || 'Pivot worker error'));\n }\n};\n\nconst ensureWorker = () => {\n if (sharedWorkerBroken) {\n return null;\n }\n\n if (typeof Worker === 'undefined') {\n log('no Worker support in this environment');\n return null;\n }\n\n if (!sharedWorker) {\n try {\n sharedWorker = new Worker(workerUrl);\n log('worker created:', workerUrl);\n } catch (error) {\n sharedWorkerBroken = true;\n log('worker creation failed:', error && error.message);\n return null;\n }\n sharedWorker.onmessage = handleWorkerMessage;\n sharedWorker.onerror = event => {\n log(\n 'worker error event (script failed to load / runtime error):',\n (event && event.message) || ''\n );\n failAllPending(new Error('Pivot worker failed to load'));\n if (!hasResultEver) {\n sharedWorkerBroken = true;\n log('worker marked unusable -> will use the synchronous fallback');\n }\n };\n if (isDebugEnabled()) {\n sharedWorker.postMessage({type: 'pivot:debug', enabled: true});\n }\n }\n return sharedWorker;\n};\n\n/*\n * Runs a full computation inside the shared worker.\n *\n * `dataKey` must change whenever the underlying dataset changes. It lets the\n * worker reuse the materialized dataset across consecutive computations and\n * lets the client skip re-staging identical data.\n */\nexport function computePivotInWorker(data, dataKey, config) {\n const worker = ensureWorker();\n if (!worker) {\n log('worker unavailable -> synchronous fallback');\n return Promise.reject(new Error('Web worker unavailable'));\n }\n const id = nextRequestId++;\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n pendingRequests.delete(id);\n log(`#${id} timed out after ${REQUEST_TIMEOUT_MS}ms`);\n reject(new Error('Pivot worker timed out'));\n }, REQUEST_TIMEOUT_MS);\n pendingRequests.set(id, {\n resolve,\n reject,\n timer,\n startedAt: Date.now(),\n });\n const mustStage = lastStagedDataKey !== dataKey;\n log(\n `#${id} dispatching to worker (dataKey=${dataKey}${\n mustStage\n ? ', staging ' + (data || []).length + ' records'\n : ', cached dataset'\n }, ${describeConfig(config)})`\n );\n if (mustStage) {\n worker.postMessage({type: 'pivot:setData', id, dataKey, data});\n lastStagedDataKey = dataKey;\n }\n worker.postMessage({type: 'pivot:compute', id, dataKey, config});\n });\n}\n\n/*\n * Same contract as the worker path, executed on the main thread. Used as the\n * automatic fallback whenever the worker path is unavailable. Deferred by\n * one tick so a loading indicator can paint before the blocking work starts.\n */\nexport function computePivotSynchronously(data, config) {\n return new Promise((resolve, reject) => {\n setTimeout(() => {\n const startedAt = Date.now();\n log('running synchronous computation on the main thread...');\n try {\n const materialized = materializeData(data, {});\n const result = computePivotState(materialized, config);\n log(`synchronous computation finished in ${Date.now() - startedAt}ms`, {\n totalRecords: result.totalRecords,\n rowKeys: result.rowKeys.length,\n colKeys: result.colKeys.length,\n });\n resolve(result);\n } catch (error) {\n log('synchronous computation failed:', error && error.message);\n reject(error);\n }\n }, 0);\n });\n}\n\n/*\n * Worker-first entry point with automatic synchronous fallback. The fallback\n * produces the exact same serialized shape, so callers render identically\n * either way.\n */\nexport function computePivotWithFallback(data, dataKey, config) {\n return computePivotInWorker(data, dataKey, config).catch(error => {\n log(\n 'worker request failed, falling back to the main thread:',\n (error && error.message) || error\n );\n return computePivotSynchronously(data, config);\n });\n}\n"]}
@@ -1114,14 +1114,12 @@ CellFormattingUI.defaultProps = {
1114
1114
  // instances share the same Web Worker.
1115
1115
  var pivotWorkerInstanceCounter = 0;
1116
1116
 
1117
- function PivotLoadingOverlay(props) {
1118
- var progress = props.progress || {};
1119
- var percent = progress.total > 0 ? Math.min(100, Math.round(progress.processed / progress.total * 100)) : null;
1120
- var phaseLabels = {
1121
- materialize: 'Reading data',
1122
- aggregate: 'Aggregating data'
1123
- };
1124
- var phaseLabel = phaseLabels[progress.phase] || 'Processing';
1117
+ // Loading overlay shown while a worker computation is running. It is removed
1118
+ // only when both conditions hold: the worker posted its `pivot:result` event
1119
+ // AND PivotTableUI has committed the freshly computed table to the DOM (see
1120
+ // the resultReady/useLayoutEffect handshake below), so content never appears
1121
+ // after the overlay disappears.
1122
+ function PivotLoadingOverlay() {
1125
1123
  return _react2.default.createElement(
1126
1124
  'div',
1127
1125
  { className: 'pvtLoadingOverlay', role: 'status' },
@@ -1129,27 +1127,11 @@ function PivotLoadingOverlay(props) {
1129
1127
  _react2.default.createElement(
1130
1128
  'div',
1131
1129
  { className: 'pvtLoadingText' },
1132
- phaseLabel,
1133
- '\u2026'
1134
- ),
1135
- percent !== null ? _react2.default.createElement(
1136
- 'div',
1137
- { className: 'pvtLoadingBarWrap' },
1138
- _react2.default.createElement('div', { className: 'pvtLoadingBar', style: { width: percent + '%' } })
1139
- ) : null,
1140
- percent !== null ? _react2.default.createElement(
1141
- 'div',
1142
- { className: 'pvtLoadingPercent' },
1143
- percent,
1144
- '%'
1145
- ) : null
1130
+ 'Processing\u2026'
1131
+ )
1146
1132
  );
1147
1133
  }
1148
1134
 
1149
- PivotLoadingOverlay.propTypes = {
1150
- progress: _propTypes2.default.object
1151
- };
1152
-
1153
1135
  function PivotTableUI(props) {
1154
1136
  var _useState9 = (0, _react.useState)([]),
1155
1137
  _useState10 = _slicedToArray(_useState9, 2),
@@ -1200,11 +1182,15 @@ function PivotTableUI(props) {
1200
1182
  _useState22 = _slicedToArray(_useState21, 2),
1201
1183
  isProcessing = _useState22[0],
1202
1184
  setIsProcessing = _useState22[1];
1185
+ // True once the worker result has been applied but the overlay is still
1186
+ // showing: the fresh table renders underneath first, then a layout effect
1187
+ // clears `isProcessing` before the browser paints.
1203
1188
 
1204
- var _useState23 = (0, _react.useState)(null),
1189
+
1190
+ var _useState23 = (0, _react.useState)(false),
1205
1191
  _useState24 = _slicedToArray(_useState23, 2),
1206
- progress = _useState24[0],
1207
- setProgress = _useState24[1];
1192
+ resultReady = _useState24[0],
1193
+ setResultReady = _useState24[1];
1208
1194
 
1209
1195
  var _useState25 = (0, _react.useState)(null),
1210
1196
  _useState26 = _slicedToArray(_useState25, 2),
@@ -1288,6 +1274,17 @@ function PivotTableUI(props) {
1288
1274
  }
1289
1275
  });
1290
1276
 
1277
+ // The loading overlay may only be turned off once the new result has been
1278
+ // rendered: the worker's `.then` sets `resultReady` (which makes this
1279
+ // render output the fresh table underneath the still-visible overlay), and
1280
+ // this layout effect runs after that DOM commit but before the browser
1281
+ // paints - so the overlay disappears exactly when the table is on screen.
1282
+ (0, _react.useLayoutEffect)(function () {
1283
+ if (resultReady && isProcessing) {
1284
+ setIsProcessing(false);
1285
+ }
1286
+ }, [resultReady, isProcessing]);
1287
+
1291
1288
  // Schedule an off-main-thread computation whenever the data or the pivot
1292
1289
  // configuration changes. Rapid consecutive changes are debounced and
1293
1290
  // stale responses are discarded.
@@ -1296,11 +1293,6 @@ function PivotTableUI(props) {
1296
1293
  // result is never applied to the new configuration or dataset.
1297
1294
  activeRequestRef.current += 1;
1298
1295
 
1299
- if (!workerEligible) {
1300
- setIsProcessing(false);
1301
- return undefined;
1302
- }
1303
-
1304
1296
  if (versionedDataRef.current !== props.data) {
1305
1297
  versionedDataRef.current = props.data;
1306
1298
  dataVersionRef.current += 1;
@@ -1314,18 +1306,13 @@ function PivotTableUI(props) {
1314
1306
 
1315
1307
  (0, _PivotDataWorker.configureWorkerUrl)(props.workerUrl || _PivotDataWorker.DEFAULT_WORKER_URL);
1316
1308
  setIsProcessing(true);
1309
+ setResultReady(false);
1317
1310
  setComputeError(null);
1318
- setProgress({ processed: 0, total: props.data.length, phase: 'queued' });
1319
1311
 
1320
1312
  var timer = setTimeout(function () {
1321
1313
  var requestId = activeRequestRef.current;
1322
1314
  var dataKey = instanceIdRef.current + '-data-' + dataVersionRef.current;
1323
- (0, _PivotDataWorker.computePivotWithFallback)(props.data, dataKey, (0, _pivotCompute.createWorkerConfig)(props), function (processed, total, phase) {
1324
- if (unmountedRef.current || requestId !== activeRequestRef.current) {
1325
- return;
1326
- }
1327
- setProgress({ processed: processed, total: total, phase: phase });
1328
- }).then(function (result) {
1315
+ (0, _PivotDataWorker.computePivotWithFallback)(props.data, dataKey, (0, _pivotCompute.createWorkerConfig)(props)).then(function (result) {
1329
1316
  if (unmountedRef.current || requestId !== activeRequestRef.current) {
1330
1317
  return;
1331
1318
  }
@@ -1334,7 +1321,10 @@ function PivotTableUI(props) {
1334
1321
  setAttrValues(result.attrValues);
1335
1322
  setMaterializedInput(result.materializedInput);
1336
1323
  setComputeError(null);
1337
- setIsProcessing(false);
1324
+ // Keep the overlay up for one more render: `resultReady` makes the
1325
+ // fresh table render underneath, and the layout effect above turns
1326
+ // `isProcessing` off only after that render has been committed.
1327
+ setResultReady(true);
1338
1328
  }).catch(function (error) {
1339
1329
  if (unmountedRef.current || requestId !== activeRequestRef.current) {
1340
1330
  return;
@@ -1346,8 +1336,7 @@ function PivotTableUI(props) {
1346
1336
  return function () {
1347
1337
  clearTimeout(timer);
1348
1338
  };
1349
- // eslint-disable-next-line react-hooks/exhaustive-deps
1350
- }, [props.data, workerEligible, computeSignature]);
1339
+ }, [props.data, computeSignature]);
1351
1340
 
1352
1341
  var sendPropUpdate = function sendPropUpdate(command) {
1353
1342
  props.onChange((0, _immutabilityHelper2.default)(props, command));
@@ -1835,7 +1824,7 @@ function PivotTableUI(props) {
1835
1824
  var showProcessing = (isProcessing || props.isShowProcessing) && !computeError;
1836
1825
  // While recomputing, re-render the last valid table (old props matched to
1837
1826
  // their own precomputed result) so the overlay sits on top of real content.
1838
- var staleView = showProcessing ? lastGoodViewRef.current : null;
1827
+ var staleView = showProcessing && !resultReady ? lastGoodViewRef.current : null;
1839
1828
 
1840
1829
  var outputContent = void 0;
1841
1830
  if (computeError) {
@@ -1862,7 +1851,7 @@ function PivotTableUI(props) {
1862
1851
  onColSort: handleColSort,
1863
1852
  onRowSort: handleRowSort
1864
1853
  }));
1865
- } else if (showProcessing) {
1854
+ } else if (showProcessing && !resultReady) {
1866
1855
  // First computation: no previous table to keep visible yet.
1867
1856
  outputContent = _react2.default.createElement('div', { className: 'pvtOutputPlaceholder' });
1868
1857
  } else {
@@ -1876,11 +1865,6 @@ function PivotTableUI(props) {
1876
1865
  }));
1877
1866
  }
1878
1867
 
1879
- // With the `isShowProcessing` testing hook active and no real computation
1880
- // running there is no progress to report, so feed the overlay a sample
1881
- // value to exercise its progress-bar and percentage branch as well.
1882
- var overlayProgress = props.isShowProcessing && !isProcessing ? progress || { processed: 60, total: 100, phase: 'aggregate' } : progress;
1883
-
1884
1868
  var outputCell = _react2.default.createElement(
1885
1869
  'td',
1886
1870
  { className: 'pvtOutput' },
@@ -1891,7 +1875,7 @@ function PivotTableUI(props) {
1891
1875
  return _react2.default.createElement(
1892
1876
  _react2.default.Fragment,
1893
1877
  null,
1894
- showProcessing ? _react2.default.createElement(PivotLoadingOverlay, { progress: overlayProgress }) : null,
1878
+ showProcessing ? _react2.default.createElement(PivotLoadingOverlay, null) : null,
1895
1879
  _react2.default.createElement(
1896
1880
  'table',
1897
1881
  { className: 'pvtUi' },
@@ -1924,26 +1908,31 @@ function PivotTableUI(props) {
1924
1908
  }
1925
1909
 
1926
1910
  return _react2.default.createElement(
1927
- 'table',
1928
- { className: 'pvtUi' },
1911
+ _react2.default.Fragment,
1912
+ null,
1913
+ showProcessing ? _react2.default.createElement(PivotLoadingOverlay, null) : null,
1929
1914
  _react2.default.createElement(
1930
- 'tbody',
1931
- { onClick: function onClick() {
1932
- return setOpenDropdown(false);
1933
- } },
1934
- _react2.default.createElement(
1935
- 'tr',
1936
- null,
1937
- rendererCell,
1938
- aggregatorCell,
1939
- colAttrsCell
1940
- ),
1915
+ 'table',
1916
+ { className: 'pvtUi' },
1941
1917
  _react2.default.createElement(
1942
- 'tr',
1943
- null,
1944
- unusedAttrsCell,
1945
- rowAttrsCell,
1946
- outputCell
1918
+ 'tbody',
1919
+ { onClick: function onClick() {
1920
+ return setOpenDropdown(false);
1921
+ } },
1922
+ _react2.default.createElement(
1923
+ 'tr',
1924
+ null,
1925
+ rendererCell,
1926
+ aggregatorCell,
1927
+ colAttrsCell
1928
+ ),
1929
+ _react2.default.createElement(
1930
+ 'tr',
1931
+ null,
1932
+ unusedAttrsCell,
1933
+ rowAttrsCell,
1934
+ outputCell
1935
+ )
1947
1936
  )
1948
1937
  )
1949
1938
  );