@refraction-ui/react 0.5.0 → 0.6.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/dist/{chunk-ZWRGVWUY.js → chunk-O4453CBF.js} +20 -3
- package/dist/chunk-O4453CBF.js.map +1 -0
- package/dist/chunk-XWP763SH.js +76 -0
- package/dist/chunk-XWP763SH.js.map +1 -0
- package/dist/faro-engine-47HGRAQH-JKINJPMH.js +3 -0
- package/dist/faro-engine-47HGRAQH-JKINJPMH.js.map +1 -0
- package/dist/form.cjs +25 -0
- package/dist/form.cjs.map +1 -1
- package/dist/form.js +9 -1
- package/dist/form.js.map +1 -1
- package/dist/index.cjs +1438 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1317 -12
- package/dist/index.js.map +1 -1
- package/dist/internal/analytics/index.d.cts +448 -0
- package/dist/internal/analytics/index.d.ts +448 -0
- package/dist/internal/logger/index.d.cts +229 -0
- package/dist/internal/logger/index.d.ts +229 -0
- package/dist/internal/react-analytics/index.d.cts +44 -0
- package/dist/internal/react-analytics/index.d.ts +44 -0
- package/dist/internal/react-logger/index.d.cts +107 -0
- package/dist/internal/react-logger/index.d.ts +107 -0
- package/dist/internal/shared/index.d.cts +171 -1
- package/dist/internal/shared/index.d.ts +171 -1
- package/dist/theme.cjs +23 -0
- package/dist/theme.cjs.map +1 -1
- package/dist/theme.js +5 -2
- package/dist/theme.js.map +1 -1
- package/package.json +4 -2
- package/dist/chunk-ZWRGVWUY.js.map +0 -1
|
@@ -188,6 +188,176 @@ declare function createSkipLink(props?: SkipLinkProps): {
|
|
|
188
188
|
className: string;
|
|
189
189
|
};
|
|
190
190
|
|
|
191
|
+
/**
|
|
192
|
+
* dev-feedback — zero-dependency `devWarn` / `devError` primitives.
|
|
193
|
+
*
|
|
194
|
+
* Design constraints (epic #247, issue #248):
|
|
195
|
+
* - Guarded by `process.env.NODE_ENV !== 'production'` so production bundlers
|
|
196
|
+
* dead-code-strip every call (the guard is a static string compare that
|
|
197
|
+
* minifiers fold to `false` in prod builds).
|
|
198
|
+
* - Warn-once dedupe per `code` — a footgun is reported once, not on every
|
|
199
|
+
* render.
|
|
200
|
+
* - NO import of `@refraction-ui/logger` (no hard dependency on the telemetry
|
|
201
|
+
* lib). Forwarding to a telemetry sink happens ONLY if the consumer
|
|
202
|
+
* explicitly injects one (dependency inversion, never an import).
|
|
203
|
+
*/
|
|
204
|
+
/**
|
|
205
|
+
* Minimal structural shape of the record a telemetry sink consumes. This is a
|
|
206
|
+
* deliberate structural mirror of `@refraction-ui/logger`'s `LogRecord` — it is
|
|
207
|
+
* NOT imported, so `@refraction-ui/shared` keeps zero dependency on the
|
|
208
|
+
* telemetry lib. A consumer that wires the real logger sink satisfies this
|
|
209
|
+
* shape structurally.
|
|
210
|
+
*/
|
|
211
|
+
interface DevFeedbackRecord {
|
|
212
|
+
level: 'warn' | 'error';
|
|
213
|
+
message: string;
|
|
214
|
+
timestamp: number;
|
|
215
|
+
/** Structured detail — the library-origin envelope lives here. */
|
|
216
|
+
context: Record<string, unknown>;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* The narrow contract a consumer-injected telemetry sink must satisfy. Kept
|
|
220
|
+
* intentionally minimal and structural so the real `TelemetrySink` from
|
|
221
|
+
* `@refraction-ui/logger` is assignable WITHOUT shared importing the logger.
|
|
222
|
+
*/
|
|
223
|
+
interface DevFeedbackSink {
|
|
224
|
+
/** Receive a single dev-feedback record. Must never throw to the caller. */
|
|
225
|
+
log(record: DevFeedbackRecord): void;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Wire an optional telemetry sink that {@link devWarn} / {@link devError}
|
|
229
|
+
* forward to (in addition to the console). Inversion of control: the consumer
|
|
230
|
+
* owns the sink; this package never imports it. Pass `null` to unwire.
|
|
231
|
+
*
|
|
232
|
+
* Forwarding still only happens in non-production (the calls themselves are
|
|
233
|
+
* stripped in prod), and still respects warn-once dedupe.
|
|
234
|
+
*/
|
|
235
|
+
declare function setDevFeedbackSink(sink: DevFeedbackSink | null): void;
|
|
236
|
+
/** Test-only / consumer-only escape hatch to reset warn-once dedupe state. */
|
|
237
|
+
declare function resetDevFeedback(): void;
|
|
238
|
+
/**
|
|
239
|
+
* Emit a development-only warning for a refraction-ui footgun.
|
|
240
|
+
*
|
|
241
|
+
* @param code Stable, greppable identifier (e.g. `'react/no-controlled-prop'`).
|
|
242
|
+
* @param message Human-readable explanation.
|
|
243
|
+
* @param detail Optional structured detail (forwarded to an injected sink).
|
|
244
|
+
*
|
|
245
|
+
* Stripped entirely in production. Warned at most once per `code`.
|
|
246
|
+
*/
|
|
247
|
+
declare function devWarn(code: string, message: string, detail?: Record<string, unknown>): void;
|
|
248
|
+
/**
|
|
249
|
+
* Emit a development-only error for a refraction-ui misuse / invariant break.
|
|
250
|
+
*
|
|
251
|
+
* @param code Stable, greppable identifier.
|
|
252
|
+
* @param message Human-readable explanation.
|
|
253
|
+
* @param detail Optional structured detail (forwarded to an injected sink).
|
|
254
|
+
*
|
|
255
|
+
* Stripped entirely in production. Reported at most once per `code`.
|
|
256
|
+
*/
|
|
257
|
+
declare function devError(code: string, message: string, detail?: Record<string, unknown>): void;
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* library-origin-error — builds the **library-origin error envelope** for
|
|
261
|
+
* errors that originate inside a `@refraction-ui/*` package.
|
|
262
|
+
*
|
|
263
|
+
* The envelope is expressed THROUGH the existing telemetry record contract
|
|
264
|
+
* (`@refraction-ui/logger`'s `LogRecord`) — the contract is reused, NOT
|
|
265
|
+
* redefined. To keep `@refraction-ui/shared` zero-dependency, the record is
|
|
266
|
+
* mirrored structurally (see {@link DevFeedbackRecord}) rather than imported;
|
|
267
|
+
* a consumer wiring the real logger sink satisfies it structurally.
|
|
268
|
+
*
|
|
269
|
+
* Aggressive redaction (epic #247 guardrails): the payload is package,
|
|
270
|
+
* componentName, version, a normalized stack **fingerprint hash**, and
|
|
271
|
+
* framework only. Never app state, props values, user data, PII, or full app
|
|
272
|
+
* stack frames.
|
|
273
|
+
*/
|
|
274
|
+
|
|
275
|
+
/** Frameworks a refraction-ui adapter can run under. */
|
|
276
|
+
type LibraryFramework = 'react' | 'angular' | 'astro' | 'vue' | 'svelte' | 'vanilla' | 'unknown';
|
|
277
|
+
/** Inputs describing where a library-origin error came from. */
|
|
278
|
+
interface LibraryOriginErrorInput {
|
|
279
|
+
/** Originating package, e.g. `@refraction-ui/react`. */
|
|
280
|
+
package: string;
|
|
281
|
+
/** refraction-ui component the error originated in, e.g. `Dialog`. */
|
|
282
|
+
componentName: string;
|
|
283
|
+
/** Originating package version, e.g. `0.1.5`. */
|
|
284
|
+
version: string;
|
|
285
|
+
/** Host framework the component was running under. */
|
|
286
|
+
framework: LibraryFramework;
|
|
287
|
+
/** The thrown error (or its stack string). Used ONLY to derive a hash. */
|
|
288
|
+
error?: unknown;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* The redacted, library-origin payload carried in `LogRecord.context`. Contains
|
|
292
|
+
* NO app data — only the five identifying fields plus a stack fingerprint hash.
|
|
293
|
+
*/
|
|
294
|
+
interface LibraryOriginEnvelope {
|
|
295
|
+
/** Marks this record as a library-origin error (for downstream routing). */
|
|
296
|
+
origin: 'refraction-ui';
|
|
297
|
+
package: string;
|
|
298
|
+
componentName: string;
|
|
299
|
+
version: string;
|
|
300
|
+
framework: LibraryFramework;
|
|
301
|
+
/** Stable, app-data-free hash of the normalized stack. */
|
|
302
|
+
fingerprint: string;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Compute the stable stack fingerprint for a library-origin error. Exposed
|
|
306
|
+
* separately so capture seams / tests can assert fingerprint stability without
|
|
307
|
+
* building the whole record.
|
|
308
|
+
*/
|
|
309
|
+
declare function stackFingerprint(error: unknown): string;
|
|
310
|
+
/**
|
|
311
|
+
* Build the redacted library-origin envelope for an error.
|
|
312
|
+
*/
|
|
313
|
+
declare function libraryOriginEnvelope(input: LibraryOriginErrorInput): LibraryOriginEnvelope;
|
|
314
|
+
/**
|
|
315
|
+
* Build the full library-origin error record, expressed THROUGH the existing
|
|
316
|
+
* telemetry record contract (`LogRecord`, mirrored as {@link DevFeedbackRecord}
|
|
317
|
+
* to avoid a hard logger dependency). The redacted envelope rides in
|
|
318
|
+
* `context`; no app data, props, PII, or app stack frames are included.
|
|
319
|
+
*
|
|
320
|
+
* This record is exactly what a consumer-wired telemetry sink (or the
|
|
321
|
+
* `devWarn`/`devError` injected sink) consumes.
|
|
322
|
+
*/
|
|
323
|
+
declare function libraryOriginError(input: LibraryOriginErrorInput): DevFeedbackRecord;
|
|
324
|
+
/**
|
|
325
|
+
* Library-origin predicate — `true` iff the error's stack contains at least
|
|
326
|
+
* one frame that references a `@refraction-ui/*` package. This is the gate
|
|
327
|
+
* every per-framework capture seam uses: only library-origin errors are
|
|
328
|
+
* eligible; the app's own errors return `false` and pass through untouched.
|
|
329
|
+
*
|
|
330
|
+
* Mirrors exactly the frame-detection rule {@link stackFingerprint} normalizes
|
|
331
|
+
* by — no new contract, just the companion predicate the seams need. Never
|
|
332
|
+
* throws for a missing/odd error (returns `false`).
|
|
333
|
+
*/
|
|
334
|
+
declare function isLibraryOriginError(error: unknown): boolean;
|
|
335
|
+
/**
|
|
336
|
+
* The fixed identity of the refraction-ui package/component a seam tags an
|
|
337
|
+
* error with. The seam supplies this; the stack itself supplies the
|
|
338
|
+
* fingerprint. Never carries app data.
|
|
339
|
+
*/
|
|
340
|
+
type LibraryOriginIdentity = Pick<LibraryOriginErrorInput, 'package' | 'componentName' | 'version' | 'framework'>;
|
|
341
|
+
/**
|
|
342
|
+
* The single capture primitive shared by every per-framework seam (React error
|
|
343
|
+
* boundary, Angular `ErrorHandler`, Astro middleware). It performs the entire
|
|
344
|
+
* guarded flow in one place so the seams stay thin and identical in behavior:
|
|
345
|
+
*
|
|
346
|
+
* 1. **Library-origin filter** — if the error's stack has no
|
|
347
|
+
* `@refraction-ui/*` frame it is the app's own error: return `null` and do
|
|
348
|
+
* NOT touch, capture, or forward it.
|
|
349
|
+
* 2. **Tag** — build the redacted {@link libraryOriginError} record
|
|
350
|
+
* (package/componentName/version/framework + app-data-free fingerprint).
|
|
351
|
+
* 3. **Route to the optional sink** — forward the record to the
|
|
352
|
+
* consumer-injected sink ONLY if one was wired; a broken sink can never
|
|
353
|
+
* break the consumer app. With no sink this is a no-op beyond returning
|
|
354
|
+
* the record (so a seam can still surface it locally).
|
|
355
|
+
*
|
|
356
|
+
* @returns the tagged record for a library-origin error, or `null` when the
|
|
357
|
+
* error is app-origin (the "pass through untouched" signal).
|
|
358
|
+
*/
|
|
359
|
+
declare function captureLibraryOriginError(error: unknown, identity: LibraryOriginIdentity, sink: DevFeedbackSink | null | undefined): DevFeedbackRecord | null;
|
|
360
|
+
|
|
191
361
|
/** Merge multiple ARIA prop objects, later values override earlier ones */
|
|
192
362
|
declare function mergeAriaProps(...propSets: Array<Record<string, unknown>>): Record<string, unknown>;
|
|
193
363
|
/**
|
|
@@ -246,4 +416,4 @@ declare function prefersReducedMotion(): boolean;
|
|
|
246
416
|
/** Get animation duration — returns '0ms' if reduced motion preferred */
|
|
247
417
|
declare function getAnimationDuration(normalDuration: string): string;
|
|
248
418
|
|
|
249
|
-
export { type AccessibilityProps, type Align, type BaseProps, type CompositionProps, type DataState, type EngineAdapter, FOCUSABLE_SELECTOR, type FocusTrap, type FocusTrapConfig, type KeyboardHandlerMap, type KeyboardKey, Keys, type LiveRegion, type LiveRegionConfig, type Machine, type MachineConfig, type Orientation, type Side, type Size, type SkipLinkProps, type ThemeProps, type TokenContract, type TokenDefinition, type Variant, cn, createFocusTrap, createKeyboardHandler, createLiveRegion, createMachine, createSkipLink, cva, generateId, getAnimationDuration, getFocusableElements, mergeAriaProps, prefersReducedMotion, resetIdCounter };
|
|
419
|
+
export { type AccessibilityProps, type Align, type BaseProps, type CompositionProps, type DataState, type DevFeedbackRecord, type DevFeedbackSink, type EngineAdapter, FOCUSABLE_SELECTOR, type FocusTrap, type FocusTrapConfig, type KeyboardHandlerMap, type KeyboardKey, Keys, type LibraryFramework, type LibraryOriginEnvelope, type LibraryOriginErrorInput, type LibraryOriginIdentity, type LiveRegion, type LiveRegionConfig, type Machine, type MachineConfig, type Orientation, type Side, type Size, type SkipLinkProps, type ThemeProps, type TokenContract, type TokenDefinition, type Variant, captureLibraryOriginError, cn, createFocusTrap, createKeyboardHandler, createLiveRegion, createMachine, createSkipLink, cva, devError, devWarn, generateId, getAnimationDuration, getFocusableElements, isLibraryOriginError, libraryOriginEnvelope, libraryOriginError, mergeAriaProps, prefersReducedMotion, resetDevFeedback, resetIdCounter, setDevFeedbackSink, stackFingerprint };
|
|
@@ -188,6 +188,176 @@ declare function createSkipLink(props?: SkipLinkProps): {
|
|
|
188
188
|
className: string;
|
|
189
189
|
};
|
|
190
190
|
|
|
191
|
+
/**
|
|
192
|
+
* dev-feedback — zero-dependency `devWarn` / `devError` primitives.
|
|
193
|
+
*
|
|
194
|
+
* Design constraints (epic #247, issue #248):
|
|
195
|
+
* - Guarded by `process.env.NODE_ENV !== 'production'` so production bundlers
|
|
196
|
+
* dead-code-strip every call (the guard is a static string compare that
|
|
197
|
+
* minifiers fold to `false` in prod builds).
|
|
198
|
+
* - Warn-once dedupe per `code` — a footgun is reported once, not on every
|
|
199
|
+
* render.
|
|
200
|
+
* - NO import of `@refraction-ui/logger` (no hard dependency on the telemetry
|
|
201
|
+
* lib). Forwarding to a telemetry sink happens ONLY if the consumer
|
|
202
|
+
* explicitly injects one (dependency inversion, never an import).
|
|
203
|
+
*/
|
|
204
|
+
/**
|
|
205
|
+
* Minimal structural shape of the record a telemetry sink consumes. This is a
|
|
206
|
+
* deliberate structural mirror of `@refraction-ui/logger`'s `LogRecord` — it is
|
|
207
|
+
* NOT imported, so `@refraction-ui/shared` keeps zero dependency on the
|
|
208
|
+
* telemetry lib. A consumer that wires the real logger sink satisfies this
|
|
209
|
+
* shape structurally.
|
|
210
|
+
*/
|
|
211
|
+
interface DevFeedbackRecord {
|
|
212
|
+
level: 'warn' | 'error';
|
|
213
|
+
message: string;
|
|
214
|
+
timestamp: number;
|
|
215
|
+
/** Structured detail — the library-origin envelope lives here. */
|
|
216
|
+
context: Record<string, unknown>;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* The narrow contract a consumer-injected telemetry sink must satisfy. Kept
|
|
220
|
+
* intentionally minimal and structural so the real `TelemetrySink` from
|
|
221
|
+
* `@refraction-ui/logger` is assignable WITHOUT shared importing the logger.
|
|
222
|
+
*/
|
|
223
|
+
interface DevFeedbackSink {
|
|
224
|
+
/** Receive a single dev-feedback record. Must never throw to the caller. */
|
|
225
|
+
log(record: DevFeedbackRecord): void;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Wire an optional telemetry sink that {@link devWarn} / {@link devError}
|
|
229
|
+
* forward to (in addition to the console). Inversion of control: the consumer
|
|
230
|
+
* owns the sink; this package never imports it. Pass `null` to unwire.
|
|
231
|
+
*
|
|
232
|
+
* Forwarding still only happens in non-production (the calls themselves are
|
|
233
|
+
* stripped in prod), and still respects warn-once dedupe.
|
|
234
|
+
*/
|
|
235
|
+
declare function setDevFeedbackSink(sink: DevFeedbackSink | null): void;
|
|
236
|
+
/** Test-only / consumer-only escape hatch to reset warn-once dedupe state. */
|
|
237
|
+
declare function resetDevFeedback(): void;
|
|
238
|
+
/**
|
|
239
|
+
* Emit a development-only warning for a refraction-ui footgun.
|
|
240
|
+
*
|
|
241
|
+
* @param code Stable, greppable identifier (e.g. `'react/no-controlled-prop'`).
|
|
242
|
+
* @param message Human-readable explanation.
|
|
243
|
+
* @param detail Optional structured detail (forwarded to an injected sink).
|
|
244
|
+
*
|
|
245
|
+
* Stripped entirely in production. Warned at most once per `code`.
|
|
246
|
+
*/
|
|
247
|
+
declare function devWarn(code: string, message: string, detail?: Record<string, unknown>): void;
|
|
248
|
+
/**
|
|
249
|
+
* Emit a development-only error for a refraction-ui misuse / invariant break.
|
|
250
|
+
*
|
|
251
|
+
* @param code Stable, greppable identifier.
|
|
252
|
+
* @param message Human-readable explanation.
|
|
253
|
+
* @param detail Optional structured detail (forwarded to an injected sink).
|
|
254
|
+
*
|
|
255
|
+
* Stripped entirely in production. Reported at most once per `code`.
|
|
256
|
+
*/
|
|
257
|
+
declare function devError(code: string, message: string, detail?: Record<string, unknown>): void;
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* library-origin-error — builds the **library-origin error envelope** for
|
|
261
|
+
* errors that originate inside a `@refraction-ui/*` package.
|
|
262
|
+
*
|
|
263
|
+
* The envelope is expressed THROUGH the existing telemetry record contract
|
|
264
|
+
* (`@refraction-ui/logger`'s `LogRecord`) — the contract is reused, NOT
|
|
265
|
+
* redefined. To keep `@refraction-ui/shared` zero-dependency, the record is
|
|
266
|
+
* mirrored structurally (see {@link DevFeedbackRecord}) rather than imported;
|
|
267
|
+
* a consumer wiring the real logger sink satisfies it structurally.
|
|
268
|
+
*
|
|
269
|
+
* Aggressive redaction (epic #247 guardrails): the payload is package,
|
|
270
|
+
* componentName, version, a normalized stack **fingerprint hash**, and
|
|
271
|
+
* framework only. Never app state, props values, user data, PII, or full app
|
|
272
|
+
* stack frames.
|
|
273
|
+
*/
|
|
274
|
+
|
|
275
|
+
/** Frameworks a refraction-ui adapter can run under. */
|
|
276
|
+
type LibraryFramework = 'react' | 'angular' | 'astro' | 'vue' | 'svelte' | 'vanilla' | 'unknown';
|
|
277
|
+
/** Inputs describing where a library-origin error came from. */
|
|
278
|
+
interface LibraryOriginErrorInput {
|
|
279
|
+
/** Originating package, e.g. `@refraction-ui/react`. */
|
|
280
|
+
package: string;
|
|
281
|
+
/** refraction-ui component the error originated in, e.g. `Dialog`. */
|
|
282
|
+
componentName: string;
|
|
283
|
+
/** Originating package version, e.g. `0.1.5`. */
|
|
284
|
+
version: string;
|
|
285
|
+
/** Host framework the component was running under. */
|
|
286
|
+
framework: LibraryFramework;
|
|
287
|
+
/** The thrown error (or its stack string). Used ONLY to derive a hash. */
|
|
288
|
+
error?: unknown;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* The redacted, library-origin payload carried in `LogRecord.context`. Contains
|
|
292
|
+
* NO app data — only the five identifying fields plus a stack fingerprint hash.
|
|
293
|
+
*/
|
|
294
|
+
interface LibraryOriginEnvelope {
|
|
295
|
+
/** Marks this record as a library-origin error (for downstream routing). */
|
|
296
|
+
origin: 'refraction-ui';
|
|
297
|
+
package: string;
|
|
298
|
+
componentName: string;
|
|
299
|
+
version: string;
|
|
300
|
+
framework: LibraryFramework;
|
|
301
|
+
/** Stable, app-data-free hash of the normalized stack. */
|
|
302
|
+
fingerprint: string;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Compute the stable stack fingerprint for a library-origin error. Exposed
|
|
306
|
+
* separately so capture seams / tests can assert fingerprint stability without
|
|
307
|
+
* building the whole record.
|
|
308
|
+
*/
|
|
309
|
+
declare function stackFingerprint(error: unknown): string;
|
|
310
|
+
/**
|
|
311
|
+
* Build the redacted library-origin envelope for an error.
|
|
312
|
+
*/
|
|
313
|
+
declare function libraryOriginEnvelope(input: LibraryOriginErrorInput): LibraryOriginEnvelope;
|
|
314
|
+
/**
|
|
315
|
+
* Build the full library-origin error record, expressed THROUGH the existing
|
|
316
|
+
* telemetry record contract (`LogRecord`, mirrored as {@link DevFeedbackRecord}
|
|
317
|
+
* to avoid a hard logger dependency). The redacted envelope rides in
|
|
318
|
+
* `context`; no app data, props, PII, or app stack frames are included.
|
|
319
|
+
*
|
|
320
|
+
* This record is exactly what a consumer-wired telemetry sink (or the
|
|
321
|
+
* `devWarn`/`devError` injected sink) consumes.
|
|
322
|
+
*/
|
|
323
|
+
declare function libraryOriginError(input: LibraryOriginErrorInput): DevFeedbackRecord;
|
|
324
|
+
/**
|
|
325
|
+
* Library-origin predicate — `true` iff the error's stack contains at least
|
|
326
|
+
* one frame that references a `@refraction-ui/*` package. This is the gate
|
|
327
|
+
* every per-framework capture seam uses: only library-origin errors are
|
|
328
|
+
* eligible; the app's own errors return `false` and pass through untouched.
|
|
329
|
+
*
|
|
330
|
+
* Mirrors exactly the frame-detection rule {@link stackFingerprint} normalizes
|
|
331
|
+
* by — no new contract, just the companion predicate the seams need. Never
|
|
332
|
+
* throws for a missing/odd error (returns `false`).
|
|
333
|
+
*/
|
|
334
|
+
declare function isLibraryOriginError(error: unknown): boolean;
|
|
335
|
+
/**
|
|
336
|
+
* The fixed identity of the refraction-ui package/component a seam tags an
|
|
337
|
+
* error with. The seam supplies this; the stack itself supplies the
|
|
338
|
+
* fingerprint. Never carries app data.
|
|
339
|
+
*/
|
|
340
|
+
type LibraryOriginIdentity = Pick<LibraryOriginErrorInput, 'package' | 'componentName' | 'version' | 'framework'>;
|
|
341
|
+
/**
|
|
342
|
+
* The single capture primitive shared by every per-framework seam (React error
|
|
343
|
+
* boundary, Angular `ErrorHandler`, Astro middleware). It performs the entire
|
|
344
|
+
* guarded flow in one place so the seams stay thin and identical in behavior:
|
|
345
|
+
*
|
|
346
|
+
* 1. **Library-origin filter** — if the error's stack has no
|
|
347
|
+
* `@refraction-ui/*` frame it is the app's own error: return `null` and do
|
|
348
|
+
* NOT touch, capture, or forward it.
|
|
349
|
+
* 2. **Tag** — build the redacted {@link libraryOriginError} record
|
|
350
|
+
* (package/componentName/version/framework + app-data-free fingerprint).
|
|
351
|
+
* 3. **Route to the optional sink** — forward the record to the
|
|
352
|
+
* consumer-injected sink ONLY if one was wired; a broken sink can never
|
|
353
|
+
* break the consumer app. With no sink this is a no-op beyond returning
|
|
354
|
+
* the record (so a seam can still surface it locally).
|
|
355
|
+
*
|
|
356
|
+
* @returns the tagged record for a library-origin error, or `null` when the
|
|
357
|
+
* error is app-origin (the "pass through untouched" signal).
|
|
358
|
+
*/
|
|
359
|
+
declare function captureLibraryOriginError(error: unknown, identity: LibraryOriginIdentity, sink: DevFeedbackSink | null | undefined): DevFeedbackRecord | null;
|
|
360
|
+
|
|
191
361
|
/** Merge multiple ARIA prop objects, later values override earlier ones */
|
|
192
362
|
declare function mergeAriaProps(...propSets: Array<Record<string, unknown>>): Record<string, unknown>;
|
|
193
363
|
/**
|
|
@@ -246,4 +416,4 @@ declare function prefersReducedMotion(): boolean;
|
|
|
246
416
|
/** Get animation duration — returns '0ms' if reduced motion preferred */
|
|
247
417
|
declare function getAnimationDuration(normalDuration: string): string;
|
|
248
418
|
|
|
249
|
-
export { type AccessibilityProps, type Align, type BaseProps, type CompositionProps, type DataState, type EngineAdapter, FOCUSABLE_SELECTOR, type FocusTrap, type FocusTrapConfig, type KeyboardHandlerMap, type KeyboardKey, Keys, type LiveRegion, type LiveRegionConfig, type Machine, type MachineConfig, type Orientation, type Side, type Size, type SkipLinkProps, type ThemeProps, type TokenContract, type TokenDefinition, type Variant, cn, createFocusTrap, createKeyboardHandler, createLiveRegion, createMachine, createSkipLink, cva, generateId, getAnimationDuration, getFocusableElements, mergeAriaProps, prefersReducedMotion, resetIdCounter };
|
|
419
|
+
export { type AccessibilityProps, type Align, type BaseProps, type CompositionProps, type DataState, type DevFeedbackRecord, type DevFeedbackSink, type EngineAdapter, FOCUSABLE_SELECTOR, type FocusTrap, type FocusTrapConfig, type KeyboardHandlerMap, type KeyboardKey, Keys, type LibraryFramework, type LibraryOriginEnvelope, type LibraryOriginErrorInput, type LibraryOriginIdentity, type LiveRegion, type LiveRegionConfig, type Machine, type MachineConfig, type Orientation, type Side, type Size, type SkipLinkProps, type ThemeProps, type TokenContract, type TokenDefinition, type Variant, captureLibraryOriginError, cn, createFocusTrap, createKeyboardHandler, createLiveRegion, createMachine, createSkipLink, cva, devError, devWarn, generateId, getAnimationDuration, getFocusableElements, isLibraryOriginError, libraryOriginEnvelope, libraryOriginError, mergeAriaProps, prefersReducedMotion, resetDevFeedback, resetIdCounter, setDevFeedbackSink, stackFingerprint };
|
package/dist/theme.cjs
CHANGED
|
@@ -137,6 +137,25 @@ function applyThemeToDOM(resolved, attribute = "class") {
|
|
|
137
137
|
root.style.colorScheme = resolved;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
// ../shared/dist/index.js
|
|
141
|
+
var seen = /* @__PURE__ */ new Set();
|
|
142
|
+
function isDev() {
|
|
143
|
+
return typeof process === "undefined" || process.env?.NODE_ENV !== "production";
|
|
144
|
+
}
|
|
145
|
+
function emit(level, code, message, detail) {
|
|
146
|
+
if (!isDev()) return;
|
|
147
|
+
const key = `${level}:${code}`;
|
|
148
|
+
if (seen.has(key)) return;
|
|
149
|
+
seen.add(key);
|
|
150
|
+
const text = `[refraction-ui] ${code}: ${message}`;
|
|
151
|
+
{
|
|
152
|
+
console.warn(text, "");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function devWarn(code, message, detail) {
|
|
156
|
+
emit("warn", code, message);
|
|
157
|
+
}
|
|
158
|
+
|
|
140
159
|
// ../react-theme/dist/index.js
|
|
141
160
|
var ThemeContext = React2__namespace.createContext(null);
|
|
142
161
|
function ThemeProvider({
|
|
@@ -180,6 +199,10 @@ function ThemeProvider({
|
|
|
180
199
|
function useTheme() {
|
|
181
200
|
const context = React2__namespace.useContext(ThemeContext);
|
|
182
201
|
if (!context) {
|
|
202
|
+
devWarn(
|
|
203
|
+
"react-theme/use-theme-outside-provider",
|
|
204
|
+
"useTheme() was called outside a <ThemeProvider>. Wrap your app (or the consuming subtree) in <ThemeProvider> so the theme context is available."
|
|
205
|
+
);
|
|
183
206
|
throw new Error("useTheme must be used within a <ThemeProvider>");
|
|
184
207
|
}
|
|
185
208
|
return context;
|
package/dist/theme.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../theme/src/theme-machine.ts","../../theme/src/theme-script.ts","../../theme/src/dom-adapters.ts","../../react-theme/src/theme-provider.tsx","../../react-theme/src/theme-toggle.tsx","../../react-theme/src/theme-script-component.tsx"],"names":["React","React2","React3"],"mappings":"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","file":"theme.cjs","sourcesContent":["/**\n * Headless theme state machine — pure TypeScript, zero DOM dependencies.\n * Manages light/dark/system mode with system preference tracking.\n */\n\nexport type ThemeMode = 'light' | 'dark' | 'system'\nexport type ResolvedTheme = 'light' | 'dark'\n\nexport interface ThemeState {\n /** User's chosen mode */\n mode: ThemeMode\n /** Resolved theme after system preference detection */\n resolved: ResolvedTheme\n}\n\nexport interface ThemeConfig {\n /** Initial mode. Default: 'system' */\n defaultMode?: ThemeMode\n /** localStorage key. Default: 'rfr-theme' */\n storageKey?: string\n /** HTML attribute to set. Default: 'class' */\n attribute?: 'class' | 'data-theme'\n}\n\nexport interface StorageAdapter {\n get(key: string): string | null\n set(key: string, value: string): void\n}\n\nexport interface MediaQueryAdapter {\n matches(query: string): boolean\n subscribe(query: string, callback: (matches: boolean) => void): () => void\n}\n\nexport interface ThemeAPI {\n /** Get current state */\n getState(): ThemeState\n /** Set mode (light/dark/system) */\n setMode(mode: ThemeMode): void\n /** Subscribe to state changes */\n subscribe(fn: (state: ThemeState) => void): () => void\n /** Clean up subscriptions */\n destroy(): void\n}\n\nfunction resolveTheme(mode: ThemeMode, systemPrefersDark: boolean): ResolvedTheme {\n if (mode === 'system') {\n return systemPrefersDark ? 'dark' : 'light'\n }\n return mode\n}\n\nexport function createTheme(\n config: ThemeConfig = {},\n storage?: StorageAdapter,\n mediaQuery?: MediaQueryAdapter,\n): ThemeAPI {\n const {\n defaultMode = 'system',\n storageKey = 'rfr-theme',\n } = config\n\n const listeners = new Set<(state: ThemeState) => void>()\n let cleanupMediaQuery: (() => void) | null = null\n\n // Read persisted mode or use default\n const persisted = storage?.get(storageKey) as ThemeMode | null\n let mode: ThemeMode = persisted && ['light', 'dark', 'system'].includes(persisted)\n ? persisted\n : defaultMode\n\n // Detect system preference\n let systemPrefersDark = mediaQuery?.matches('(prefers-color-scheme: dark)') ?? false\n\n let state: ThemeState = {\n mode,\n resolved: resolveTheme(mode, systemPrefersDark),\n }\n\n function notify() {\n for (const fn of listeners) {\n fn(state)\n }\n }\n\n function updateState(newMode: ThemeMode) {\n mode = newMode\n state = { mode, resolved: resolveTheme(mode, systemPrefersDark) }\n storage?.set(storageKey, mode)\n notify()\n }\n\n // Listen for system preference changes\n if (mediaQuery) {\n cleanupMediaQuery = mediaQuery.subscribe(\n '(prefers-color-scheme: dark)',\n (matches) => {\n systemPrefersDark = matches\n if (mode === 'system') {\n state = { mode, resolved: resolveTheme(mode, systemPrefersDark) }\n notify()\n }\n },\n )\n }\n\n return {\n getState() {\n return state\n },\n\n setMode(newMode: ThemeMode) {\n if (newMode !== mode) {\n updateState(newMode)\n }\n },\n\n subscribe(fn: (state: ThemeState) => void) {\n listeners.add(fn)\n return () => {\n listeners.delete(fn)\n }\n },\n\n destroy() {\n listeners.clear()\n cleanupMediaQuery?.()\n },\n }\n}\n","/**\n * Inline script for preventing theme flash on page load.\n * Inject this as a <script> tag in the <head> before any CSS.\n * Works with any framework (React, Angular, Astro, plain HTML).\n */\n\nexport function getThemeScript(\n storageKey = 'rfr-theme',\n attribute: 'class' | 'data-theme' = 'class',\n): string {\n // This string is injected as innerHTML of a <script> tag.\n // It runs before any CSS/JS loads, preventing flash of wrong theme.\n return `(function(){try{var m=localStorage.getItem('${storageKey}');var s=window.matchMedia('(prefers-color-scheme:dark)').matches;var t=m==='dark'||(m!=='light'&&s)?'dark':'light';var d=document.documentElement;${\n attribute === 'class'\n ? \"d.classList.remove('light','dark');d.classList.add(t);\"\n : `d.setAttribute('${attribute}',t);`\n }d.style.colorScheme=t;}catch(e){}})()`\n}\n","/**\n * Browser DOM adapters for the theme machine.\n * These bridge the headless core to browser APIs.\n */\n\nimport type { StorageAdapter, MediaQueryAdapter, ResolvedTheme } from './theme-machine.js'\n\n/** localStorage adapter */\nexport function createLocalStorageAdapter(): StorageAdapter {\n return {\n get(key) {\n try {\n return localStorage.getItem(key)\n } catch {\n return null\n }\n },\n set(key, value) {\n try {\n localStorage.setItem(key, value)\n } catch {\n // localStorage unavailable (SSR, incognito quota exceeded, etc.)\n }\n },\n }\n}\n\n/** matchMedia adapter */\nexport function createMediaQueryAdapter(): MediaQueryAdapter {\n return {\n matches(query) {\n if (typeof window === 'undefined') return false\n return window.matchMedia(query).matches\n },\n subscribe(query, callback) {\n if (typeof window === 'undefined') return () => {}\n const mql = window.matchMedia(query)\n const handler = (e: MediaQueryListEvent) => callback(e.matches)\n mql.addEventListener('change', handler)\n return () => mql.removeEventListener('change', handler)\n },\n }\n}\n\n/** Apply resolved theme to the document */\nexport function applyThemeToDOM(\n resolved: ResolvedTheme,\n attribute: 'class' | 'data-theme' = 'class',\n): void {\n if (typeof document === 'undefined') return\n\n const root = document.documentElement\n if (attribute === 'class') {\n root.classList.remove('light', 'dark')\n root.classList.add(resolved)\n } else {\n root.setAttribute(attribute, resolved)\n }\n root.style.colorScheme = resolved\n}\n","import * as React from 'react'\nimport {\n createTheme,\n createLocalStorageAdapter,\n createMediaQueryAdapter,\n applyThemeToDOM,\n type ThemeMode,\n type ResolvedTheme,\n type ThemeConfig,\n type ThemeAPI,\n} from '@refraction-ui/theme'\n\nexport interface ThemeContextValue {\n mode: ThemeMode\n resolved: ResolvedTheme\n setMode: (mode: ThemeMode) => void\n}\n\nconst ThemeContext = React.createContext<ThemeContextValue | null>(null)\n\nexport interface ThemeProviderProps extends ThemeConfig {\n children: React.ReactNode\n}\n\nexport function ThemeProvider({\n children,\n defaultMode = 'system',\n storageKey = 'rfr-theme',\n attribute = 'class',\n}: ThemeProviderProps) {\n const themeRef = React.useRef<ThemeAPI | null>(null)\n\n // Initialize theme machine once (client-side only for adapters)\n if (!themeRef.current) {\n const isBrowser = typeof window !== 'undefined'\n themeRef.current = createTheme(\n { defaultMode, storageKey, attribute },\n isBrowser ? createLocalStorageAdapter() : undefined,\n isBrowser ? createMediaQueryAdapter() : undefined,\n )\n }\n\n const [state, setState] = React.useState(() => themeRef.current!.getState())\n\n React.useEffect(() => {\n const theme = themeRef.current!\n // Apply initial theme to DOM\n applyThemeToDOM(theme.getState().resolved, attribute)\n\n // Subscribe to changes\n const unsub = theme.subscribe((newState) => {\n setState(newState)\n applyThemeToDOM(newState.resolved, attribute)\n })\n\n return () => {\n unsub()\n theme.destroy()\n }\n }, [attribute])\n\n const contextValue = React.useMemo<ThemeContextValue>(\n () => ({\n mode: state.mode,\n resolved: state.resolved,\n setMode: (mode: ThemeMode) => themeRef.current?.setMode(mode),\n }),\n [state.mode, state.resolved],\n )\n\n return React.createElement(ThemeContext.Provider, { value: contextValue }, children)\n}\n\nexport function useTheme(): ThemeContextValue {\n const context = React.useContext(ThemeContext)\n if (!context) {\n throw new Error('useTheme must be used within a <ThemeProvider>')\n }\n return context\n}\n","import * as React from 'react'\nimport { useTheme } from './theme-provider.js'\nimport type { ThemeMode } from '@refraction-ui/theme'\n\nconst modes: { value: ThemeMode; label: string; icon: string }[] = [\n { value: 'light', label: 'Light', icon: 'sun' },\n { value: 'dark', label: 'Dark', icon: 'moon' },\n { value: 'system', label: 'System', icon: 'monitor' },\n]\n\n// Inline SVG icons — no external icon library dependency\nconst icons: Record<string, React.ReactNode> = {\n sun: React.createElement('svg', {\n xmlns: 'http://www.w3.org/2000/svg', width: 16, height: 16, viewBox: '0 0 24 24',\n fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round',\n },\n React.createElement('circle', { cx: 12, cy: 12, r: 5 }),\n React.createElement('path', { d: 'M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42' }),\n ),\n moon: React.createElement('svg', {\n xmlns: 'http://www.w3.org/2000/svg', width: 16, height: 16, viewBox: '0 0 24 24',\n fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round',\n },\n React.createElement('path', { d: 'M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z' }),\n ),\n monitor: React.createElement('svg', {\n xmlns: 'http://www.w3.org/2000/svg', width: 16, height: 16, viewBox: '0 0 24 24',\n fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round',\n },\n React.createElement('rect', { x: 2, y: 3, width: 20, height: 14, rx: 2, ry: 2 }),\n React.createElement('line', { x1: 8, y1: 21, x2: 16, y2: 21 }),\n React.createElement('line', { x1: 12, y1: 17, x2: 12, y2: 21 }),\n ),\n}\n\nexport interface ThemeToggleProps {\n className?: string\n /** 'dropdown' shows a menu, 'segmented' shows inline buttons */\n variant?: 'dropdown' | 'segmented'\n}\n\nexport function ThemeToggle({ className, variant = 'segmented' }: ThemeToggleProps) {\n const { mode, setMode } = useTheme()\n\n if (variant === 'segmented') {\n return React.createElement('div', {\n className: `inline-flex items-center gap-1 rounded-lg border p-1 ${className ?? ''}`,\n role: 'radiogroup',\n 'aria-label': 'Theme',\n },\n modes.map(({ value, label, icon }) =>\n React.createElement('button', {\n key: value,\n type: 'button',\n role: 'radio',\n 'aria-checked': mode === value,\n 'aria-label': label,\n className: `inline-flex items-center justify-center rounded-md p-1.5 text-sm transition-colors ${\n mode === value\n ? 'bg-accent text-accent-foreground'\n : 'text-muted-foreground hover:bg-muted'\n }`,\n onClick: () => setMode(value),\n }, icons[icon]),\n ),\n )\n }\n\n // Dropdown variant — simplified, no external dropdown dependency\n const [open, setOpen] = React.useState(false)\n const ref = React.useRef<HTMLDivElement>(null)\n\n React.useEffect(() => {\n if (!open) return\n const handler = (e: MouseEvent) => {\n if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false)\n }\n document.addEventListener('mousedown', handler)\n return () => document.removeEventListener('mousedown', handler)\n }, [open])\n\n const currentIcon = modes.find((m) => m.value === mode)?.icon ?? 'monitor'\n\n return React.createElement('div', { ref, className: `relative ${className ?? ''}` },\n React.createElement('button', {\n type: 'button',\n 'aria-label': 'Toggle theme',\n 'aria-expanded': open,\n className: 'inline-flex items-center justify-center rounded-md p-2 text-sm transition-colors hover:bg-muted',\n onClick: () => setOpen(!open),\n }, icons[currentIcon]),\n open && React.createElement('div', {\n className: 'absolute right-0 top-full mt-1 z-50 min-w-[8rem] rounded-md border bg-popover p-1 shadow-md',\n role: 'menu',\n },\n modes.map(({ value, label, icon }) =>\n React.createElement('button', {\n key: value,\n type: 'button',\n role: 'menuitem',\n className: `flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm transition-colors hover:bg-accent ${\n mode === value ? 'bg-accent' : ''\n }`,\n onClick: () => { setMode(value); setOpen(false) },\n }, icons[icon], label),\n ),\n ),\n )\n}\n","import * as React from 'react'\nimport { getThemeScript } from '@refraction-ui/theme'\n\nexport interface ThemeScriptProps {\n storageKey?: string\n attribute?: 'class' | 'data-theme'\n}\n\n/**\n * Renders an inline <script> that prevents theme flash on SSR pages.\n * Place this in the <head> of your document (in Next.js layout.tsx, Remix root, etc.)\n */\nexport function ThemeScript({\n storageKey = 'rfr-theme',\n attribute = 'class',\n}: ThemeScriptProps) {\n return React.createElement('script', {\n dangerouslySetInnerHTML: {\n __html: getThemeScript(storageKey, attribute),\n },\n })\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../theme/src/theme-machine.ts","../../theme/src/theme-script.ts","../../theme/src/dom-adapters.ts","../../shared/src/dev-feedback.ts","../../react-theme/src/theme-provider.tsx","../../react-theme/src/theme-toggle.tsx","../../react-theme/src/theme-script-component.tsx"],"names":["React","React2","React3"],"mappings":"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","file":"theme.cjs","sourcesContent":["/**\n * Headless theme state machine — pure TypeScript, zero DOM dependencies.\n * Manages light/dark/system mode with system preference tracking.\n */\n\nexport type ThemeMode = 'light' | 'dark' | 'system'\nexport type ResolvedTheme = 'light' | 'dark'\n\nexport interface ThemeState {\n /** User's chosen mode */\n mode: ThemeMode\n /** Resolved theme after system preference detection */\n resolved: ResolvedTheme\n}\n\nexport interface ThemeConfig {\n /** Initial mode. Default: 'system' */\n defaultMode?: ThemeMode\n /** localStorage key. Default: 'rfr-theme' */\n storageKey?: string\n /** HTML attribute to set. Default: 'class' */\n attribute?: 'class' | 'data-theme'\n}\n\nexport interface StorageAdapter {\n get(key: string): string | null\n set(key: string, value: string): void\n}\n\nexport interface MediaQueryAdapter {\n matches(query: string): boolean\n subscribe(query: string, callback: (matches: boolean) => void): () => void\n}\n\nexport interface ThemeAPI {\n /** Get current state */\n getState(): ThemeState\n /** Set mode (light/dark/system) */\n setMode(mode: ThemeMode): void\n /** Subscribe to state changes */\n subscribe(fn: (state: ThemeState) => void): () => void\n /** Clean up subscriptions */\n destroy(): void\n}\n\nfunction resolveTheme(mode: ThemeMode, systemPrefersDark: boolean): ResolvedTheme {\n if (mode === 'system') {\n return systemPrefersDark ? 'dark' : 'light'\n }\n return mode\n}\n\nexport function createTheme(\n config: ThemeConfig = {},\n storage?: StorageAdapter,\n mediaQuery?: MediaQueryAdapter,\n): ThemeAPI {\n const {\n defaultMode = 'system',\n storageKey = 'rfr-theme',\n } = config\n\n const listeners = new Set<(state: ThemeState) => void>()\n let cleanupMediaQuery: (() => void) | null = null\n\n // Read persisted mode or use default\n const persisted = storage?.get(storageKey) as ThemeMode | null\n let mode: ThemeMode = persisted && ['light', 'dark', 'system'].includes(persisted)\n ? persisted\n : defaultMode\n\n // Detect system preference\n let systemPrefersDark = mediaQuery?.matches('(prefers-color-scheme: dark)') ?? false\n\n let state: ThemeState = {\n mode,\n resolved: resolveTheme(mode, systemPrefersDark),\n }\n\n function notify() {\n for (const fn of listeners) {\n fn(state)\n }\n }\n\n function updateState(newMode: ThemeMode) {\n mode = newMode\n state = { mode, resolved: resolveTheme(mode, systemPrefersDark) }\n storage?.set(storageKey, mode)\n notify()\n }\n\n // Listen for system preference changes\n if (mediaQuery) {\n cleanupMediaQuery = mediaQuery.subscribe(\n '(prefers-color-scheme: dark)',\n (matches) => {\n systemPrefersDark = matches\n if (mode === 'system') {\n state = { mode, resolved: resolveTheme(mode, systemPrefersDark) }\n notify()\n }\n },\n )\n }\n\n return {\n getState() {\n return state\n },\n\n setMode(newMode: ThemeMode) {\n if (newMode !== mode) {\n updateState(newMode)\n }\n },\n\n subscribe(fn: (state: ThemeState) => void) {\n listeners.add(fn)\n return () => {\n listeners.delete(fn)\n }\n },\n\n destroy() {\n listeners.clear()\n cleanupMediaQuery?.()\n },\n }\n}\n","/**\n * Inline script for preventing theme flash on page load.\n * Inject this as a <script> tag in the <head> before any CSS.\n * Works with any framework (React, Angular, Astro, plain HTML).\n */\n\nexport function getThemeScript(\n storageKey = 'rfr-theme',\n attribute: 'class' | 'data-theme' = 'class',\n): string {\n // This string is injected as innerHTML of a <script> tag.\n // It runs before any CSS/JS loads, preventing flash of wrong theme.\n return `(function(){try{var m=localStorage.getItem('${storageKey}');var s=window.matchMedia('(prefers-color-scheme:dark)').matches;var t=m==='dark'||(m!=='light'&&s)?'dark':'light';var d=document.documentElement;${\n attribute === 'class'\n ? \"d.classList.remove('light','dark');d.classList.add(t);\"\n : `d.setAttribute('${attribute}',t);`\n }d.style.colorScheme=t;}catch(e){}})()`\n}\n","/**\n * Browser DOM adapters for the theme machine.\n * These bridge the headless core to browser APIs.\n */\n\nimport type { StorageAdapter, MediaQueryAdapter, ResolvedTheme } from './theme-machine.js'\n\n/** localStorage adapter */\nexport function createLocalStorageAdapter(): StorageAdapter {\n return {\n get(key) {\n try {\n return localStorage.getItem(key)\n } catch {\n return null\n }\n },\n set(key, value) {\n try {\n localStorage.setItem(key, value)\n } catch {\n // localStorage unavailable (SSR, incognito quota exceeded, etc.)\n }\n },\n }\n}\n\n/** matchMedia adapter */\nexport function createMediaQueryAdapter(): MediaQueryAdapter {\n return {\n matches(query) {\n if (typeof window === 'undefined') return false\n return window.matchMedia(query).matches\n },\n subscribe(query, callback) {\n if (typeof window === 'undefined') return () => {}\n const mql = window.matchMedia(query)\n const handler = (e: MediaQueryListEvent) => callback(e.matches)\n mql.addEventListener('change', handler)\n return () => mql.removeEventListener('change', handler)\n },\n }\n}\n\n/** Apply resolved theme to the document */\nexport function applyThemeToDOM(\n resolved: ResolvedTheme,\n attribute: 'class' | 'data-theme' = 'class',\n): void {\n if (typeof document === 'undefined') return\n\n const root = document.documentElement\n if (attribute === 'class') {\n root.classList.remove('light', 'dark')\n root.classList.add(resolved)\n } else {\n root.setAttribute(attribute, resolved)\n }\n root.style.colorScheme = resolved\n}\n","/**\n * dev-feedback — zero-dependency `devWarn` / `devError` primitives.\n *\n * Design constraints (epic #247, issue #248):\n * - Guarded by `process.env.NODE_ENV !== 'production'` so production bundlers\n * dead-code-strip every call (the guard is a static string compare that\n * minifiers fold to `false` in prod builds).\n * - Warn-once dedupe per `code` — a footgun is reported once, not on every\n * render.\n * - NO import of `@refraction-ui/logger` (no hard dependency on the telemetry\n * lib). Forwarding to a telemetry sink happens ONLY if the consumer\n * explicitly injects one (dependency inversion, never an import).\n */\n\n/**\n * Minimal structural shape of the record a telemetry sink consumes. This is a\n * deliberate structural mirror of `@refraction-ui/logger`'s `LogRecord` — it is\n * NOT imported, so `@refraction-ui/shared` keeps zero dependency on the\n * telemetry lib. A consumer that wires the real logger sink satisfies this\n * shape structurally.\n */\nexport interface DevFeedbackRecord {\n level: 'warn' | 'error'\n message: string\n timestamp: number\n /** Structured detail — the library-origin envelope lives here. */\n context: Record<string, unknown>\n}\n\n/**\n * The narrow contract a consumer-injected telemetry sink must satisfy. Kept\n * intentionally minimal and structural so the real `TelemetrySink` from\n * `@refraction-ui/logger` is assignable WITHOUT shared importing the logger.\n */\nexport interface DevFeedbackSink {\n /** Receive a single dev-feedback record. Must never throw to the caller. */\n log(record: DevFeedbackRecord): void\n}\n\n/**\n * Minimal ambient view of `process.env` so we can read `NODE_ENV` WITHOUT\n * pulling `@types/node` into this zero-dependency package. Accessed defensively\n * (the `typeof process === 'undefined'` guard) so this is safe in browsers too.\n */\ndeclare const process:\n | { env?: { NODE_ENV?: string } }\n | undefined\n\n/** Per-code dedupe set — module-scoped so it survives across calls. */\nconst seen = new Set<string>()\n\n/**\n * Optional, consumer-injected sink. `null` until a consumer explicitly wires\n * one via {@link setDevFeedbackSink}. Nothing phones home implicitly.\n */\nlet injectedSink: DevFeedbackSink | null = null\n\n/**\n * Wire an optional telemetry sink that {@link devWarn} / {@link devError}\n * forward to (in addition to the console). Inversion of control: the consumer\n * owns the sink; this package never imports it. Pass `null` to unwire.\n *\n * Forwarding still only happens in non-production (the calls themselves are\n * stripped in prod), and still respects warn-once dedupe.\n */\nexport function setDevFeedbackSink(sink: DevFeedbackSink | null): void {\n injectedSink = sink\n}\n\n/** Test-only / consumer-only escape hatch to reset warn-once dedupe state. */\nexport function resetDevFeedback(): void {\n seen.clear()\n}\n\nfunction isDev(): boolean {\n // String compare (not a negated truthiness) so bundlers can statically fold\n // `process.env.NODE_ENV` and strip the whole branch in production builds.\n return (\n typeof process === 'undefined' ||\n process.env?.NODE_ENV !== 'production'\n )\n}\n\nfunction emit(\n level: 'warn' | 'error',\n code: string,\n message: string,\n detail?: Record<string, unknown>,\n): void {\n if (!isDev()) return\n\n // Warn-once dedupe, keyed by (level, code) so an error and a warning sharing\n // a code are not collapsed into one.\n const key = `${level}:${code}`\n if (seen.has(key)) return\n seen.add(key)\n\n const text = `[refraction-ui] ${code}: ${message}`\n\n if (level === 'error') {\n console.error(text, detail ?? '')\n } else {\n console.warn(text, detail ?? '')\n }\n\n // Forward to the consumer-injected sink ONLY if one was explicitly wired.\n if (injectedSink) {\n const record: DevFeedbackRecord = {\n level,\n message: `${code}: ${message}`,\n timestamp: Date.now(),\n context: { code, ...(detail ?? {}) },\n }\n try {\n injectedSink.log(record)\n } catch {\n // A broken sink must never break the consumer app.\n }\n }\n}\n\n/**\n * Emit a development-only warning for a refraction-ui footgun.\n *\n * @param code Stable, greppable identifier (e.g. `'react/no-controlled-prop'`).\n * @param message Human-readable explanation.\n * @param detail Optional structured detail (forwarded to an injected sink).\n *\n * Stripped entirely in production. Warned at most once per `code`.\n */\nexport function devWarn(\n code: string,\n message: string,\n detail?: Record<string, unknown>,\n): void {\n emit('warn', code, message, detail)\n}\n\n/**\n * Emit a development-only error for a refraction-ui misuse / invariant break.\n *\n * @param code Stable, greppable identifier.\n * @param message Human-readable explanation.\n * @param detail Optional structured detail (forwarded to an injected sink).\n *\n * Stripped entirely in production. Reported at most once per `code`.\n */\nexport function devError(\n code: string,\n message: string,\n detail?: Record<string, unknown>,\n): void {\n emit('error', code, message, detail)\n}\n","import * as React from 'react'\nimport {\n createTheme,\n createLocalStorageAdapter,\n createMediaQueryAdapter,\n applyThemeToDOM,\n type ThemeMode,\n type ResolvedTheme,\n type ThemeConfig,\n type ThemeAPI,\n} from '@refraction-ui/theme'\nimport { devWarn } from '@refraction-ui/shared'\n\nexport interface ThemeContextValue {\n mode: ThemeMode\n resolved: ResolvedTheme\n setMode: (mode: ThemeMode) => void\n}\n\nconst ThemeContext = React.createContext<ThemeContextValue | null>(null)\n\nexport interface ThemeProviderProps extends ThemeConfig {\n children: React.ReactNode\n}\n\nexport function ThemeProvider({\n children,\n defaultMode = 'system',\n storageKey = 'rfr-theme',\n attribute = 'class',\n}: ThemeProviderProps) {\n const themeRef = React.useRef<ThemeAPI | null>(null)\n\n // Initialize theme machine once (client-side only for adapters)\n if (!themeRef.current) {\n const isBrowser = typeof window !== 'undefined'\n themeRef.current = createTheme(\n { defaultMode, storageKey, attribute },\n isBrowser ? createLocalStorageAdapter() : undefined,\n isBrowser ? createMediaQueryAdapter() : undefined,\n )\n }\n\n const [state, setState] = React.useState(() => themeRef.current!.getState())\n\n React.useEffect(() => {\n const theme = themeRef.current!\n // Apply initial theme to DOM\n applyThemeToDOM(theme.getState().resolved, attribute)\n\n // Subscribe to changes\n const unsub = theme.subscribe((newState) => {\n setState(newState)\n applyThemeToDOM(newState.resolved, attribute)\n })\n\n return () => {\n unsub()\n theme.destroy()\n }\n }, [attribute])\n\n const contextValue = React.useMemo<ThemeContextValue>(\n () => ({\n mode: state.mode,\n resolved: state.resolved,\n setMode: (mode: ThemeMode) => themeRef.current?.setMode(mode),\n }),\n [state.mode, state.resolved],\n )\n\n return React.createElement(ThemeContext.Provider, { value: contextValue }, children)\n}\n\nexport function useTheme(): ThemeContextValue {\n const context = React.useContext(ThemeContext)\n if (!context) {\n devWarn(\n 'react-theme/use-theme-outside-provider',\n 'useTheme() was called outside a <ThemeProvider>. Wrap your app (or the consuming subtree) in <ThemeProvider> so the theme context is available.',\n )\n throw new Error('useTheme must be used within a <ThemeProvider>')\n }\n return context\n}\n","import * as React from 'react'\nimport { useTheme } from './theme-provider.js'\nimport type { ThemeMode } from '@refraction-ui/theme'\n\nconst modes: { value: ThemeMode; label: string; icon: string }[] = [\n { value: 'light', label: 'Light', icon: 'sun' },\n { value: 'dark', label: 'Dark', icon: 'moon' },\n { value: 'system', label: 'System', icon: 'monitor' },\n]\n\n// Inline SVG icons — no external icon library dependency\nconst icons: Record<string, React.ReactNode> = {\n sun: React.createElement('svg', {\n xmlns: 'http://www.w3.org/2000/svg', width: 16, height: 16, viewBox: '0 0 24 24',\n fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round',\n },\n React.createElement('circle', { cx: 12, cy: 12, r: 5 }),\n React.createElement('path', { d: 'M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42' }),\n ),\n moon: React.createElement('svg', {\n xmlns: 'http://www.w3.org/2000/svg', width: 16, height: 16, viewBox: '0 0 24 24',\n fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round',\n },\n React.createElement('path', { d: 'M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z' }),\n ),\n monitor: React.createElement('svg', {\n xmlns: 'http://www.w3.org/2000/svg', width: 16, height: 16, viewBox: '0 0 24 24',\n fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round',\n },\n React.createElement('rect', { x: 2, y: 3, width: 20, height: 14, rx: 2, ry: 2 }),\n React.createElement('line', { x1: 8, y1: 21, x2: 16, y2: 21 }),\n React.createElement('line', { x1: 12, y1: 17, x2: 12, y2: 21 }),\n ),\n}\n\nexport interface ThemeToggleProps {\n className?: string\n /** 'dropdown' shows a menu, 'segmented' shows inline buttons */\n variant?: 'dropdown' | 'segmented'\n}\n\nexport function ThemeToggle({ className, variant = 'segmented' }: ThemeToggleProps) {\n const { mode, setMode } = useTheme()\n\n if (variant === 'segmented') {\n return React.createElement('div', {\n className: `inline-flex items-center gap-1 rounded-lg border p-1 ${className ?? ''}`,\n role: 'radiogroup',\n 'aria-label': 'Theme',\n },\n modes.map(({ value, label, icon }) =>\n React.createElement('button', {\n key: value,\n type: 'button',\n role: 'radio',\n 'aria-checked': mode === value,\n 'aria-label': label,\n className: `inline-flex items-center justify-center rounded-md p-1.5 text-sm transition-colors ${\n mode === value\n ? 'bg-accent text-accent-foreground'\n : 'text-muted-foreground hover:bg-muted'\n }`,\n onClick: () => setMode(value),\n }, icons[icon]),\n ),\n )\n }\n\n // Dropdown variant — simplified, no external dropdown dependency\n const [open, setOpen] = React.useState(false)\n const ref = React.useRef<HTMLDivElement>(null)\n\n React.useEffect(() => {\n if (!open) return\n const handler = (e: MouseEvent) => {\n if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false)\n }\n document.addEventListener('mousedown', handler)\n return () => document.removeEventListener('mousedown', handler)\n }, [open])\n\n const currentIcon = modes.find((m) => m.value === mode)?.icon ?? 'monitor'\n\n return React.createElement('div', { ref, className: `relative ${className ?? ''}` },\n React.createElement('button', {\n type: 'button',\n 'aria-label': 'Toggle theme',\n 'aria-expanded': open,\n className: 'inline-flex items-center justify-center rounded-md p-2 text-sm transition-colors hover:bg-muted',\n onClick: () => setOpen(!open),\n }, icons[currentIcon]),\n open && React.createElement('div', {\n className: 'absolute right-0 top-full mt-1 z-50 min-w-[8rem] rounded-md border bg-popover p-1 shadow-md',\n role: 'menu',\n },\n modes.map(({ value, label, icon }) =>\n React.createElement('button', {\n key: value,\n type: 'button',\n role: 'menuitem',\n className: `flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm transition-colors hover:bg-accent ${\n mode === value ? 'bg-accent' : ''\n }`,\n onClick: () => { setMode(value); setOpen(false) },\n }, icons[icon], label),\n ),\n ),\n )\n}\n","import * as React from 'react'\nimport { getThemeScript } from '@refraction-ui/theme'\n\nexport interface ThemeScriptProps {\n storageKey?: string\n attribute?: 'class' | 'data-theme'\n}\n\n/**\n * Renders an inline <script> that prevents theme flash on SSR pages.\n * Place this in the <head> of your document (in Next.js layout.tsx, Remix root, etc.)\n */\nexport function ThemeScript({\n storageKey = 'rfr-theme',\n attribute = 'class',\n}: ThemeScriptProps) {\n return React.createElement('script', {\n dangerouslySetInnerHTML: {\n __html: getThemeScript(storageKey, attribute),\n },\n })\n}\n"]}
|
package/dist/theme.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
+
import { devWarn } from './chunk-O4453CBF.js';
|
|
1
2
|
import * as React2 from 'react';
|
|
2
3
|
|
|
3
|
-
// ../react-theme/dist/index.js
|
|
4
|
-
|
|
5
4
|
// ../theme/dist/index.js
|
|
6
5
|
function resolveTheme(mode, systemPrefersDark) {
|
|
7
6
|
if (mode === "system") {
|
|
@@ -158,6 +157,10 @@ function ThemeProvider({
|
|
|
158
157
|
function useTheme() {
|
|
159
158
|
const context = React2.useContext(ThemeContext);
|
|
160
159
|
if (!context) {
|
|
160
|
+
devWarn(
|
|
161
|
+
"react-theme/use-theme-outside-provider",
|
|
162
|
+
"useTheme() was called outside a <ThemeProvider>. Wrap your app (or the consuming subtree) in <ThemeProvider> so the theme context is available."
|
|
163
|
+
);
|
|
161
164
|
throw new Error("useTheme must be used within a <ThemeProvider>");
|
|
162
165
|
}
|
|
163
166
|
return context;
|