fontdue-js 3.0.6 → 3.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/CHANGELOG.md +17 -0
- package/dist/__generated__/FeatureTesterCard_fontStyle.graphql.d.ts +23 -0
- package/dist/__generated__/FeatureTesterCard_fontStyle.graphql.js +62 -0
- package/dist/__generated__/FeatureTesterStandaloneQuery.graphql.d.ts +25 -0
- package/dist/__generated__/FeatureTesterStandaloneQuery.graphql.js +240 -0
- package/dist/__generated__/FeatureTestersIdQuery.graphql.d.ts +21 -0
- package/dist/__generated__/FeatureTestersIdQuery.graphql.js +344 -0
- package/dist/__generated__/FeatureTestersSlugQuery.graphql.d.ts +25 -0
- package/dist/__generated__/FeatureTestersSlugQuery.graphql.js +364 -0
- package/dist/__generated__/FeatureTesters_collection.graphql.d.ts +39 -0
- package/dist/__generated__/FeatureTesters_collection.graphql.js +145 -0
- package/dist/__generated__/NodePasswordFormAccessNodeMutation.graphql.d.ts +2 -1
- package/dist/__generated__/NodePasswordFormAccessNodeMutation.graphql.js +10 -4
- package/dist/__generated__/NodePasswordFormElementIDQuery.graphql.d.ts +21 -0
- package/dist/__generated__/NodePasswordFormElementIDQuery.graphql.js +86 -0
- package/dist/__generated__/NodePasswordFormElementSlugQuery.graphql.d.ts +25 -0
- package/dist/__generated__/NodePasswordFormElementSlugQuery.graphql.js +108 -0
- package/dist/__tests__/createFontdueFetch.test.js +128 -4
- package/dist/__tests__/highlight.test.js +141 -0
- package/dist/__tests__/middleware.test.js +132 -0
- package/dist/__tests__/nextAdapter.test.js +115 -1
- package/dist/components/FeatureTester/FeatureTesterCard.d.ts +30 -0
- package/dist/components/FeatureTester/FeatureTesterCard.js +167 -0
- package/dist/components/FeatureTester/FeatureTesterElement.d.ts +19 -0
- package/dist/components/FeatureTester/FeatureTesterElement.js +42 -0
- package/dist/components/FeatureTester/FeatureTesters.d.ts +43 -0
- package/dist/components/FeatureTester/FeatureTesters.js +156 -0
- package/dist/components/FeatureTester/FeatureTesters.server.d.ts +9 -0
- package/dist/components/FeatureTester/FeatureTesters.server.js +40 -0
- package/dist/components/FeatureTester/FeatureTestersElement.d.ts +10 -0
- package/dist/components/FeatureTester/FeatureTestersElement.js +27 -0
- package/dist/components/FeatureTester/highlight.d.ts +19 -0
- package/dist/components/FeatureTester/highlight.js +71 -0
- package/dist/components/FeatureTester/index.d.ts +26 -0
- package/dist/components/FeatureTester/index.js +66 -0
- package/dist/components/FeatureTester/index.server.d.ts +7 -0
- package/dist/components/FeatureTester/index.server.js +30 -0
- package/dist/components/NodePasswordForm/NodePasswordFormElement.d.ts +3 -0
- package/dist/components/NodePasswordForm/NodePasswordFormElement.js +65 -0
- package/dist/components/NodePasswordForm/index.d.ts +27 -1
- package/dist/components/NodePasswordForm/index.js +54 -60
- package/dist/components/Root/index.js +2 -1
- package/dist/fontdue.css +145 -0
- package/dist/hooks/useFeatureTesterAutofit.d.ts +17 -0
- package/dist/hooks/useFeatureTesterAutofit.js +106 -0
- package/dist/next/index.d.ts +2 -0
- package/dist/next/index.js +19 -3
- package/dist/next/registerSingleTenantResolver.js +13 -1
- package/dist/next/tenant.d.ts +1 -0
- package/dist/next/tenant.js +58 -2
- package/dist/next/unlock.d.ts +3 -0
- package/dist/next/unlock.js +49 -0
- package/dist/nodeAccess.d.ts +18 -0
- package/dist/nodeAccess.js +84 -0
- package/dist/preview/server.d.ts +1 -20
- package/dist/preview/server.js +16 -85
- package/dist/relay/environment.js +8 -2
- package/dist/relay/serverConfig.d.ts +3 -0
- package/dist/relay/serverConfig.js +22 -1
- package/dist/server/index.d.ts +8 -0
- package/dist/server/index.js +90 -7
- package/dist/server/middleware.d.ts +37 -0
- package/dist/server/middleware.js +144 -0
- package/package.json +9 -2
- package/types/next-headers.d.ts +5 -1
|
@@ -48,17 +48,23 @@ vi.mock('next/navigation', () => ({
|
|
|
48
48
|
const draft = vi.hoisted(() => ({
|
|
49
49
|
enabled: false,
|
|
50
50
|
token: undefined,
|
|
51
|
+
nodeAccess: undefined,
|
|
51
52
|
cookiesError: undefined
|
|
52
53
|
}));
|
|
53
54
|
vi.mock('next/headers', () => ({
|
|
54
55
|
draftMode: async () => ({
|
|
55
|
-
isEnabled: draft.enabled
|
|
56
|
+
isEnabled: draft.enabled,
|
|
57
|
+
enable: () => {
|
|
58
|
+
draft.enabled = true;
|
|
59
|
+
}
|
|
56
60
|
}),
|
|
57
61
|
cookies: async () => {
|
|
58
62
|
if (draft.cookiesError) throw draft.cookiesError;
|
|
59
63
|
return {
|
|
60
64
|
get: name => name === 'fontdue_preview_token' && draft.token ? {
|
|
61
65
|
value: draft.token
|
|
66
|
+
} : name === 'fontdue_node_access' && draft.nodeAccess ? {
|
|
67
|
+
value: draft.nodeAccess
|
|
62
68
|
} : undefined
|
|
63
69
|
};
|
|
64
70
|
}
|
|
@@ -74,6 +80,7 @@ beforeEach(() => {
|
|
|
74
80
|
vi.restoreAllMocks();
|
|
75
81
|
draft.enabled = false;
|
|
76
82
|
draft.token = undefined;
|
|
83
|
+
draft.nodeAccess = undefined;
|
|
77
84
|
draft.cookiesError = undefined;
|
|
78
85
|
});
|
|
79
86
|
function stubSingleTenant() {
|
|
@@ -360,6 +367,113 @@ describe('single-tenant ambient resolver (no per-render call)', () => {
|
|
|
360
367
|
expect(config === null || config === void 0 ? void 0 : (_config$headers5 = config.headers) === null || _config$headers5 === void 0 ? void 0 : _config$headers5.authorization).toBeUndefined();
|
|
361
368
|
});
|
|
362
369
|
});
|
|
370
|
+
describe('nodeAccessHeaders', () => {
|
|
371
|
+
it('draft-mode bypass render: returns the forwarding header', async () => {
|
|
372
|
+
stubSingleTenant();
|
|
373
|
+
draft.enabled = true;
|
|
374
|
+
draft.nodeAccess = 'na-tok';
|
|
375
|
+
const {
|
|
376
|
+
nodeAccessHeaders
|
|
377
|
+
} = await importTenant();
|
|
378
|
+
expect(await nodeAccessHeaders()).toEqual({
|
|
379
|
+
'fontdue-node-access': 'na-tok'
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
it('draft-mode bypass render without the cookie: returns {}', async () => {
|
|
383
|
+
stubSingleTenant();
|
|
384
|
+
draft.enabled = true;
|
|
385
|
+
const {
|
|
386
|
+
nodeAccessHeaders
|
|
387
|
+
} = await importTenant();
|
|
388
|
+
expect(await nodeAccessHeaders()).toEqual({});
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
// Regression for FD-773: locked slugs are excluded from generateStaticParams,
|
|
392
|
+
// so a visitor's first request is an on-demand STATIC fill of the SSG route.
|
|
393
|
+
// Merely calling cookies() there flags the render as dynamic and Next 15
|
|
394
|
+
// 500s ("Page changed from static to dynamic at runtime") — catching the
|
|
395
|
+
// throw doesn't unpoison it. Off draft mode the hook must return "no token"
|
|
396
|
+
// WITHOUT touching cookies(), so the fill can prerender the password form.
|
|
397
|
+
// cookiesError guards the "without touching" half: any cookies() call would
|
|
398
|
+
// reject.
|
|
399
|
+
it('runtime static fill (no draft mode): no token, and cookies() is never called', async () => {
|
|
400
|
+
stubSingleTenant();
|
|
401
|
+
draft.cookiesError = Object.assign(new Error('Dynamic server usage'), {
|
|
402
|
+
digest: 'DYNAMIC_SERVER_USAGE'
|
|
403
|
+
});
|
|
404
|
+
const {
|
|
405
|
+
nodeAccessHeaders
|
|
406
|
+
} = await importTenant();
|
|
407
|
+
expect(await nodeAccessHeaders()).toEqual({});
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// At build time the gate is skipped and the bailout keeps propagating: a
|
|
411
|
+
// static route whose own fetch touches a locked collection bails out to
|
|
412
|
+
// dynamic at build today, and the fix must not change build classification.
|
|
413
|
+
it('build-time prerender: still propagates the dynamic bailout', async () => {
|
|
414
|
+
stubSingleTenant();
|
|
415
|
+
vi.stubEnv('NEXT_PHASE', 'phase-production-build');
|
|
416
|
+
draft.cookiesError = Object.assign(new Error('Dynamic server usage'), {
|
|
417
|
+
digest: 'DYNAMIC_SERVER_USAGE'
|
|
418
|
+
});
|
|
419
|
+
const {
|
|
420
|
+
nodeAccessHeaders
|
|
421
|
+
} = await importTenant();
|
|
422
|
+
await expect(nodeAccessHeaders()).rejects.toBe(draft.cookiesError);
|
|
423
|
+
});
|
|
424
|
+
it('swallows a non-control-flow throw (no request scope)', async () => {
|
|
425
|
+
stubSingleTenant();
|
|
426
|
+
draft.enabled = true;
|
|
427
|
+
draft.cookiesError = new Error('no request scope');
|
|
428
|
+
const {
|
|
429
|
+
nodeAccessHeaders
|
|
430
|
+
} = await importTenant();
|
|
431
|
+
expect(await nodeAccessHeaders()).toEqual({});
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
describe('unlock route handler', () => {
|
|
435
|
+
async function importUnlock() {
|
|
436
|
+
return await import("../next/unlock.js");
|
|
437
|
+
}
|
|
438
|
+
function unlockRequest(body) {
|
|
439
|
+
return new Request('https://storefront.example/api/unlock', {
|
|
440
|
+
method: 'POST',
|
|
441
|
+
headers: {
|
|
442
|
+
'content-type': 'application/json'
|
|
443
|
+
},
|
|
444
|
+
body
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
it('POST with a token enables the draft-mode bypass', async () => {
|
|
448
|
+
const {
|
|
449
|
+
POST
|
|
450
|
+
} = await importUnlock();
|
|
451
|
+
const response = await POST(unlockRequest(JSON.stringify({
|
|
452
|
+
token: 'na-tok'
|
|
453
|
+
})));
|
|
454
|
+
expect(response.status).toBe(200);
|
|
455
|
+
expect(await response.json()).toEqual({
|
|
456
|
+
ok: true
|
|
457
|
+
});
|
|
458
|
+
expect(draft.enabled).toBe(true);
|
|
459
|
+
});
|
|
460
|
+
it('rejects a missing token without enabling the bypass', async () => {
|
|
461
|
+
const {
|
|
462
|
+
POST
|
|
463
|
+
} = await importUnlock();
|
|
464
|
+
const response = await POST(unlockRequest(JSON.stringify({})));
|
|
465
|
+
expect(response.status).toBe(400);
|
|
466
|
+
expect(draft.enabled).toBe(false);
|
|
467
|
+
});
|
|
468
|
+
it('rejects a malformed body without enabling the bypass', async () => {
|
|
469
|
+
const {
|
|
470
|
+
POST
|
|
471
|
+
} = await importUnlock();
|
|
472
|
+
const response = await POST(unlockRequest('not json'));
|
|
473
|
+
expect(response.status).toBe(400);
|
|
474
|
+
expect(draft.enabled).toBe(false);
|
|
475
|
+
});
|
|
476
|
+
});
|
|
363
477
|
|
|
364
478
|
// withFontdue detects the route-tree shape from the working directory; give
|
|
365
479
|
// it one with or without src/app/[domain].
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { FeatureTesterCard_fontStyle$key } from '../../__generated__/FeatureTesterCard_fontStyle.graphql.js';
|
|
3
|
+
export type ToggleMode = 'button' | 'hover';
|
|
4
|
+
export interface FeatureTesterCardOptions {
|
|
5
|
+
feature: string;
|
|
6
|
+
content: string;
|
|
7
|
+
label?: string | null;
|
|
8
|
+
fontSize?: number | null;
|
|
9
|
+
lineHeight?: number | null;
|
|
10
|
+
letterSpacing?: number | null;
|
|
11
|
+
alignment?: 'left' | 'center' | 'right' | null;
|
|
12
|
+
defaultOn?: boolean;
|
|
13
|
+
highlightColor?: string | null;
|
|
14
|
+
showLabel?: boolean;
|
|
15
|
+
showTag?: boolean;
|
|
16
|
+
toggle?: ToggleMode;
|
|
17
|
+
autofit?: boolean;
|
|
18
|
+
changedRanges?: ReadonlyArray<{
|
|
19
|
+
readonly start: number;
|
|
20
|
+
readonly length: number;
|
|
21
|
+
}> | null;
|
|
22
|
+
variableSettings?: ReadonlyArray<{
|
|
23
|
+
readonly axis: string;
|
|
24
|
+
readonly value: number;
|
|
25
|
+
}> | null;
|
|
26
|
+
}
|
|
27
|
+
export type FeatureTesterCardProps = FeatureTesterCardOptions & {
|
|
28
|
+
fontStyle: FeatureTesterCard_fontStyle$key;
|
|
29
|
+
};
|
|
30
|
+
export default function FeatureTesterCard({ fontStyle: fontStyleKey, feature, content, label, fontSize, lineHeight, letterSpacing, alignment, defaultOn, highlightColor, showLabel, showTag, toggle, autofit, changedRanges, variableSettings, }: FeatureTesterCardProps): React.JSX.Element;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
4
|
+
import _FeatureTesterCard_fontStyle from "../../__generated__/FeatureTesterCard_fontStyle.graphql.js";
|
|
5
|
+
import React, { useMemo, useState } from 'react';
|
|
6
|
+
import { graphql, useFragment } from 'react-relay';
|
|
7
|
+
import FontStyle from '../FontStyle/index.js';
|
|
8
|
+
import useFeaturesData from '../TypeTester/useFeaturesData.js';
|
|
9
|
+
import useFeatureTesterAutofit from '../../hooks/useFeatureTesterAutofit.js';
|
|
10
|
+
import { sensitiveStringsForFeature, computeSensitivityFlags, segmentByFlags } from './highlight.js';
|
|
11
|
+
const fragment = (_FeatureTesterCard_fontStyle.hash && _FeatureTesterCard_fontStyle.hash !== "687d8afca93d615ed0794f963702d082" && console.error("The definition of 'FeatureTesterCard_fontStyle' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _FeatureTesterCard_fontStyle);
|
|
12
|
+
export default function FeatureTesterCard(_ref) {
|
|
13
|
+
let {
|
|
14
|
+
fontStyle: fontStyleKey,
|
|
15
|
+
feature,
|
|
16
|
+
content,
|
|
17
|
+
label,
|
|
18
|
+
fontSize = 72,
|
|
19
|
+
lineHeight = 1.2,
|
|
20
|
+
letterSpacing = 0,
|
|
21
|
+
alignment,
|
|
22
|
+
defaultOn = true,
|
|
23
|
+
highlightColor,
|
|
24
|
+
showLabel = true,
|
|
25
|
+
showTag = true,
|
|
26
|
+
toggle = 'button',
|
|
27
|
+
autofit = false,
|
|
28
|
+
changedRanges,
|
|
29
|
+
variableSettings
|
|
30
|
+
} = _ref;
|
|
31
|
+
// `data` carries the merged fragment spreads, so the child fragment hooks
|
|
32
|
+
// (FontStyle, useFeaturesData) read their refs off it rather than the raw key.
|
|
33
|
+
const data = useFragment(fragment, fontStyleKey);
|
|
34
|
+
const {
|
|
35
|
+
featureNames
|
|
36
|
+
} = useFeaturesData({
|
|
37
|
+
fontStyle: data
|
|
38
|
+
});
|
|
39
|
+
const [on, setOn] = useState(defaultOn);
|
|
40
|
+
const hover = toggle === 'hover';
|
|
41
|
+
const handleToggle = () => setOn(o => !o);
|
|
42
|
+
|
|
43
|
+
// The server's shaped diff (`changedRanges`) is exact for THIS string —
|
|
44
|
+
// context-free and contextual substitutions alike — so it wins whenever the
|
|
45
|
+
// collection materialized it. The static GSUB map is the fallback for cards
|
|
46
|
+
// with no shaped diff (no OpenType file server-side, or a hand-authored
|
|
47
|
+
// standalone embed); it only covers single + ligature substitutions, and for
|
|
48
|
+
// a mixed feature like frac it can hold just the slash while the real
|
|
49
|
+
// substitution is contextual over the digits — trusting it over the diff
|
|
50
|
+
// would highlight (and per-span apply) the wrong subset.
|
|
51
|
+
const staticStrings = useMemo(() => sensitiveStringsForFeature(data.glyphNames, feature), [data.glyphNames, feature]);
|
|
52
|
+
const contextFree = staticStrings.length > 0;
|
|
53
|
+
const changedMarks = useMemo(() => (changedRanges ?? []).map(r => ({
|
|
54
|
+
start: r.start,
|
|
55
|
+
end: r.start + r.length - 1
|
|
56
|
+
})), [changedRanges]);
|
|
57
|
+
const contextual = changedMarks.length > 0;
|
|
58
|
+
const segments = useMemo(() => segmentByFlags(content,
|
|
59
|
+
// A card with a shaped diff applies the feature container-wide (a
|
|
60
|
+
// per-span apply would sever the context contextual substitutions
|
|
61
|
+
// depend on), so the set that actually changes is exactly the diff —
|
|
62
|
+
// the highlight equals that set. The static-map fallback applies
|
|
63
|
+
// per-span.
|
|
64
|
+
contextual ? computeSensitivityFlags(content, {
|
|
65
|
+
addMarks: changedMarks
|
|
66
|
+
}) : computeSensitivityFlags(content, {
|
|
67
|
+
autoStrings: staticStrings
|
|
68
|
+
})), [contextual, content, staticStrings, changedMarks]);
|
|
69
|
+
const displayLabel = label || featureNames[feature] || feature;
|
|
70
|
+
const featureSetting = `"${feature}" ${on ? 1 : 0}`;
|
|
71
|
+
|
|
72
|
+
// The chosen variable instance's coordinates, as a font-variation-settings
|
|
73
|
+
// value. Applied to the sample and to the autofit measurement so both see
|
|
74
|
+
// the same glyph widths.
|
|
75
|
+
const fontVariationSettings = useMemo(() => variableSettings && variableSettings.length > 0 ? variableSettings.map(s => `"${s.axis}" ${s.value}`).join(', ') : undefined, [variableSettings]);
|
|
76
|
+
|
|
77
|
+
// A contextual feature with no shaped diff and no static entries (a
|
|
78
|
+
// hand-authored standalone embed) has nothing to tint, but the feature must
|
|
79
|
+
// still apply container-wide or the toggle would do nothing.
|
|
80
|
+
const anySensitive = segments.some(seg => seg.sensitive);
|
|
81
|
+
const containerWide = contextual || !contextFree && !anySensitive;
|
|
82
|
+
|
|
83
|
+
// Autofit measures the sample against the card's width in both toggle states
|
|
84
|
+
// and returns a size that fits the wider one; disabled, it returns `fontSize`.
|
|
85
|
+
const {
|
|
86
|
+
ref: rootRef,
|
|
87
|
+
fontSize: renderSize
|
|
88
|
+
} = useFeatureTesterAutofit({
|
|
89
|
+
enabled: autofit,
|
|
90
|
+
text: content,
|
|
91
|
+
fontFamily: `${data.cssFamily} ${data.name}`,
|
|
92
|
+
feature,
|
|
93
|
+
fontSize: fontSize ?? 72,
|
|
94
|
+
letterSpacing: letterSpacing ?? 0,
|
|
95
|
+
fontVariationSettings
|
|
96
|
+
});
|
|
97
|
+
return /*#__PURE__*/React.createElement("div", _extends({
|
|
98
|
+
ref: rootRef,
|
|
99
|
+
className: "fontdue-feature-tester",
|
|
100
|
+
"data-feature": feature,
|
|
101
|
+
"data-on": on,
|
|
102
|
+
"data-toggle": toggle
|
|
103
|
+
// Drives the muted-context styling: only a card with affected glyphs
|
|
104
|
+
// dims its surroundings, so an unmarked hand-authored sample stays at
|
|
105
|
+
// full text colour.
|
|
106
|
+
,
|
|
107
|
+
"data-highlighted": anySensitive
|
|
108
|
+
}, hover ? {
|
|
109
|
+
onMouseEnter: () => setOn(!defaultOn),
|
|
110
|
+
onMouseLeave: () => setOn(defaultOn)
|
|
111
|
+
} : {}), showLabel && /*#__PURE__*/React.createElement("div", {
|
|
112
|
+
className: "fontdue-feature-tester__bar"
|
|
113
|
+
}, /*#__PURE__*/React.createElement("button", {
|
|
114
|
+
type: "button",
|
|
115
|
+
className: "fontdue-feature-tester__toggle",
|
|
116
|
+
role: "switch",
|
|
117
|
+
"aria-checked": on,
|
|
118
|
+
"aria-label": displayLabel,
|
|
119
|
+
onClick: handleToggle
|
|
120
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
121
|
+
className: "fontdue-feature-tester__switch",
|
|
122
|
+
"aria-hidden": "true"
|
|
123
|
+
})), /*#__PURE__*/React.createElement("span", {
|
|
124
|
+
className: "fontdue-feature-tester__label",
|
|
125
|
+
onClick: hover ? undefined : handleToggle
|
|
126
|
+
}, displayLabel), showTag && /*#__PURE__*/React.createElement("span", {
|
|
127
|
+
className: "fontdue-feature-tester__tag"
|
|
128
|
+
}, feature)), /*#__PURE__*/React.createElement(FontStyle, {
|
|
129
|
+
Component: "div",
|
|
130
|
+
fontStyle: data,
|
|
131
|
+
className: "fontdue-feature-tester__sample",
|
|
132
|
+
style: {
|
|
133
|
+
fontSize: `${renderSize}px`,
|
|
134
|
+
lineHeight: lineHeight ?? undefined,
|
|
135
|
+
letterSpacing: letterSpacing ? `${letterSpacing}em` : undefined,
|
|
136
|
+
textAlign: alignment ?? undefined,
|
|
137
|
+
fontVariationSettings,
|
|
138
|
+
// Contextual features must shape with full context, so the feature is
|
|
139
|
+
// applied to the whole line. The shaped diff guarantees only the
|
|
140
|
+
// highlighted characters actually change, so the highlight still
|
|
141
|
+
// means exactly "what changes".
|
|
142
|
+
...(containerWide ? {
|
|
143
|
+
fontFeatureSettings: featureSetting
|
|
144
|
+
} : {})
|
|
145
|
+
}
|
|
146
|
+
}, segments.map((seg, i) => seg.sensitive ? /*#__PURE__*/React.createElement("span", {
|
|
147
|
+
key: i,
|
|
148
|
+
className: "fontdue-feature-tester__hit"
|
|
149
|
+
// Context-free features are applied per-highlighted-span, so
|
|
150
|
+
// toggling changes exactly what's tinted (and honours manual
|
|
151
|
+
// subsetting). `font-feature-settings` leaves unlisted (default)
|
|
152
|
+
// features alone, so default ligatures/kerning are preserved. The
|
|
153
|
+
// highlight colour comes from CSS (`--fontdue-feature-tester-
|
|
154
|
+
// highlight`); `highlightColor` forces it inline when set.
|
|
155
|
+
,
|
|
156
|
+
style: {
|
|
157
|
+
...(highlightColor ? {
|
|
158
|
+
color: highlightColor
|
|
159
|
+
} : {}),
|
|
160
|
+
...(containerWide ? {} : {
|
|
161
|
+
fontFeatureSettings: featureSetting
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
}, seg.text) : /*#__PURE__*/React.createElement(React.Fragment, {
|
|
165
|
+
key: i
|
|
166
|
+
}, seg.text))));
|
|
167
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface FeatureTesterElement_props {
|
|
3
|
+
familyName: string;
|
|
4
|
+
styleName: string;
|
|
5
|
+
feature: string;
|
|
6
|
+
content?: string;
|
|
7
|
+
label?: string;
|
|
8
|
+
fontSize?: string;
|
|
9
|
+
lineHeight?: string;
|
|
10
|
+
letterSpacing?: string;
|
|
11
|
+
defaultOn?: string;
|
|
12
|
+
highlightColor?: string;
|
|
13
|
+
showLabel?: string;
|
|
14
|
+
showTag?: string;
|
|
15
|
+
toggle?: string;
|
|
16
|
+
autofit?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare const FeatureTesterElement: React.FC<FeatureTesterElement_props>;
|
|
19
|
+
export default FeatureTesterElement;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Deliberately unexposed: this standalone element is not registered in the
|
|
2
|
+
// Root custom-element registry and `fontdue-js/FeatureTester` is not in the
|
|
3
|
+
// package exports. Only the collection-bound `<fontdue-feature-testers>`
|
|
4
|
+
// ships for now; this file is kept for when the standalone is ready.
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import FeatureTester from './index.js';
|
|
7
|
+
const parseBool = (input, fallback) => input === undefined || input === '' ? fallback : input === 'true';
|
|
8
|
+
export const FeatureTesterElement = _ref => {
|
|
9
|
+
let {
|
|
10
|
+
familyName,
|
|
11
|
+
styleName,
|
|
12
|
+
feature,
|
|
13
|
+
content,
|
|
14
|
+
label,
|
|
15
|
+
fontSize,
|
|
16
|
+
lineHeight,
|
|
17
|
+
letterSpacing,
|
|
18
|
+
defaultOn,
|
|
19
|
+
highlightColor,
|
|
20
|
+
showLabel,
|
|
21
|
+
showTag,
|
|
22
|
+
toggle,
|
|
23
|
+
autofit
|
|
24
|
+
} = _ref;
|
|
25
|
+
return /*#__PURE__*/React.createElement(FeatureTester, {
|
|
26
|
+
familyName: familyName,
|
|
27
|
+
styleName: styleName,
|
|
28
|
+
feature: feature,
|
|
29
|
+
content: content || 'Hamburgefonts',
|
|
30
|
+
label: label || undefined,
|
|
31
|
+
fontSize: fontSize ? parseFloat(fontSize) : undefined,
|
|
32
|
+
lineHeight: lineHeight ? parseFloat(lineHeight) : undefined,
|
|
33
|
+
letterSpacing: letterSpacing ? parseFloat(letterSpacing) : undefined,
|
|
34
|
+
defaultOn: parseBool(defaultOn, true),
|
|
35
|
+
highlightColor: highlightColor || undefined,
|
|
36
|
+
showLabel: parseBool(showLabel, true),
|
|
37
|
+
showTag: parseBool(showTag, true),
|
|
38
|
+
toggle: toggle === 'hover' ? 'hover' : 'button',
|
|
39
|
+
autofit: parseBool(autofit, false)
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
export default FeatureTesterElement;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Config } from '../ConfigContext.js';
|
|
3
|
+
import { FeatureTestersIdQuery } from '../../__generated__/FeatureTestersIdQuery.graphql.js';
|
|
4
|
+
import { FeatureTestersSlugQuery } from '../../__generated__/FeatureTestersSlugQuery.graphql.js';
|
|
5
|
+
import { ToggleMode } from './FeatureTesterCard.js';
|
|
6
|
+
import { SerializablePreloadedQuery, type LoadQueryOptions } from '../../relay/loadSerializableQuery.js';
|
|
7
|
+
interface ListOptions {
|
|
8
|
+
highlightColor?: string;
|
|
9
|
+
toggle?: ToggleMode;
|
|
10
|
+
autofit?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export type FeatureTestersPreloadedQuery = SerializablePreloadedQuery<FeatureTestersIdQuery> | SerializablePreloadedQuery<FeatureTestersSlugQuery>;
|
|
13
|
+
export type LoadFeatureTestersQueryVariables = {
|
|
14
|
+
collectionId: string;
|
|
15
|
+
collectionSlug?: never;
|
|
16
|
+
} | {
|
|
17
|
+
collectionId?: never;
|
|
18
|
+
collectionSlug: string;
|
|
19
|
+
};
|
|
20
|
+
export declare function loadFeatureTestersQuery(variables: LoadFeatureTestersQueryVariables, options?: LoadQueryOptions): Promise<FeatureTestersPreloadedQuery>;
|
|
21
|
+
export declare function FeatureTestersPreloadedIDQueryRenderer({ preloadedQuery, ...options }: {
|
|
22
|
+
preloadedQuery: SerializablePreloadedQuery<FeatureTestersIdQuery>;
|
|
23
|
+
} & ListOptions): React.JSX.Element | null;
|
|
24
|
+
export declare function FeatureTestersPreloadedSlugQueryRenderer({ preloadedQuery, ...options }: {
|
|
25
|
+
preloadedQuery: SerializablePreloadedQuery<FeatureTestersSlugQuery>;
|
|
26
|
+
} & ListOptions): React.JSX.Element | null;
|
|
27
|
+
export type FeatureTestersProps = {
|
|
28
|
+
config?: Config;
|
|
29
|
+
} & ListOptions & ({
|
|
30
|
+
preloadedQuery: FeatureTestersPreloadedQuery;
|
|
31
|
+
collectionId?: never;
|
|
32
|
+
collectionSlug?: never;
|
|
33
|
+
} | {
|
|
34
|
+
preloadedQuery?: never;
|
|
35
|
+
collectionId: string;
|
|
36
|
+
collectionSlug?: never;
|
|
37
|
+
} | {
|
|
38
|
+
preloadedQuery?: never;
|
|
39
|
+
collectionSlug: string;
|
|
40
|
+
collectionId?: never;
|
|
41
|
+
});
|
|
42
|
+
export default function FeatureTesters({ config, ...props }: FeatureTestersProps): React.JSX.Element;
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
4
|
+
import _FeatureTestersSlugQuery from "../../__generated__/FeatureTestersSlugQuery.graphql.js";
|
|
5
|
+
import _FeatureTestersIdQuery from "../../__generated__/FeatureTestersIdQuery.graphql.js";
|
|
6
|
+
import _FeatureTesters_collection from "../../__generated__/FeatureTesters_collection.graphql.js";
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import { graphql, useFragment, useLazyLoadQuery, usePreloadedQuery } from 'react-relay';
|
|
9
|
+
import FeatureTesterCard from './FeatureTesterCard.js';
|
|
10
|
+
import { EnsureFontdueContext } from '../FontdueContextProvider/index.js';
|
|
11
|
+
import FeatureTestersIdQueryNode from '../../__generated__/FeatureTestersIdQuery.graphql.js';
|
|
12
|
+
import FeatureTestersSlugQueryNode from '../../__generated__/FeatureTestersSlugQuery.graphql.js';
|
|
13
|
+
import loadSerializableQuery from '../../relay/loadSerializableQuery.js';
|
|
14
|
+
import useSerializablePreloadedQuery from '../../relay/useSerializablePreloadedQuery.js';
|
|
15
|
+
const collectionFragment = (_FeatureTesters_collection.hash && _FeatureTesters_collection.hash !== "933c122b193ca5df430509d87e9c4e1d" && console.error("The definition of 'FeatureTesters_collection' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _FeatureTesters_collection);
|
|
16
|
+
function FeatureTestersList(_ref) {
|
|
17
|
+
let {
|
|
18
|
+
collection,
|
|
19
|
+
highlightColor,
|
|
20
|
+
toggle,
|
|
21
|
+
autofit
|
|
22
|
+
} = _ref;
|
|
23
|
+
const data = useFragment(collectionFragment, collection);
|
|
24
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, data.featureTesters.map(ft => ft.fontStyle ? /*#__PURE__*/React.createElement(FeatureTesterCard, {
|
|
25
|
+
key: ft.id,
|
|
26
|
+
fontStyle: ft.fontStyle,
|
|
27
|
+
feature: ft.feature,
|
|
28
|
+
label: ft.label ?? undefined,
|
|
29
|
+
content: ft.content,
|
|
30
|
+
fontSize: ft.size != null ? Number(ft.size) : undefined,
|
|
31
|
+
lineHeight: ft.lineHeight != null ? Number(ft.lineHeight) : undefined,
|
|
32
|
+
alignment: ft.alignment === 'left' || ft.alignment === 'center' || ft.alignment === 'right' ? ft.alignment : undefined,
|
|
33
|
+
defaultOn: data.featureTesterDefaultOn,
|
|
34
|
+
changedRanges: ft.changedRanges,
|
|
35
|
+
variableSettings: ft.variableSettings,
|
|
36
|
+
highlightColor: highlightColor,
|
|
37
|
+
toggle: toggle,
|
|
38
|
+
autofit: autofit ?? ft.autofit,
|
|
39
|
+
showTag: data.featureTesterShowTag
|
|
40
|
+
}) : null));
|
|
41
|
+
}
|
|
42
|
+
const idQuery = (_FeatureTestersIdQuery.hash && _FeatureTestersIdQuery.hash !== "55f152ae85db4ebafd21ff7945f4079a" && console.error("The definition of 'FeatureTestersIdQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _FeatureTestersIdQuery);
|
|
43
|
+
const slugQuery = (_FeatureTestersSlugQuery.hash && _FeatureTestersSlugQuery.hash !== "726fca18c4a30ca9dd8f5aaf074c8139" && console.error("The definition of 'FeatureTestersSlugQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _FeatureTestersSlugQuery);
|
|
44
|
+
function ById(_ref2) {
|
|
45
|
+
let {
|
|
46
|
+
collectionId,
|
|
47
|
+
...rest
|
|
48
|
+
} = _ref2;
|
|
49
|
+
const data = useLazyLoadQuery(idQuery, {
|
|
50
|
+
id: collectionId
|
|
51
|
+
});
|
|
52
|
+
return data.node ? /*#__PURE__*/React.createElement(FeatureTestersList, _extends({
|
|
53
|
+
collection: data.node
|
|
54
|
+
}, rest)) : null;
|
|
55
|
+
}
|
|
56
|
+
function BySlug(_ref3) {
|
|
57
|
+
var _data$viewer, _data$viewer$slug;
|
|
58
|
+
let {
|
|
59
|
+
collectionSlug,
|
|
60
|
+
...rest
|
|
61
|
+
} = _ref3;
|
|
62
|
+
const data = useLazyLoadQuery(slugQuery, {
|
|
63
|
+
slug: collectionSlug
|
|
64
|
+
});
|
|
65
|
+
const coll = (_data$viewer = data.viewer) === null || _data$viewer === void 0 ? void 0 : (_data$viewer$slug = _data$viewer.slug) === null || _data$viewer$slug === void 0 ? void 0 : _data$viewer$slug.fontCollection;
|
|
66
|
+
return coll ? /*#__PURE__*/React.createElement(FeatureTestersList, _extends({
|
|
67
|
+
collection: coll
|
|
68
|
+
}, rest)) : null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---- Server-preload path (RSC): the server awaits the query and hands the
|
|
72
|
+
// serialized response to these client renderers, which commit it into the store
|
|
73
|
+
// so `usePreloadedQuery` resolves synchronously on the client (no refetch on
|
|
74
|
+
// hydration). Mirrors CharacterViewer.
|
|
75
|
+
|
|
76
|
+
export async function loadFeatureTestersQuery(variables, options) {
|
|
77
|
+
if (variables.collectionId) {
|
|
78
|
+
return loadSerializableQuery(FeatureTestersIdQueryNode, {
|
|
79
|
+
id: variables.collectionId
|
|
80
|
+
}, options);
|
|
81
|
+
}
|
|
82
|
+
if (variables.collectionSlug) {
|
|
83
|
+
return loadSerializableQuery(FeatureTestersSlugQueryNode, {
|
|
84
|
+
slug: variables.collectionSlug
|
|
85
|
+
}, options);
|
|
86
|
+
}
|
|
87
|
+
throw new Error('loadFeatureTestersQuery expected either collectionId or collectionSlug');
|
|
88
|
+
}
|
|
89
|
+
export function FeatureTestersPreloadedIDQueryRenderer(_ref4) {
|
|
90
|
+
let {
|
|
91
|
+
preloadedQuery,
|
|
92
|
+
...options
|
|
93
|
+
} = _ref4;
|
|
94
|
+
const queryRef = useSerializablePreloadedQuery(preloadedQuery, 'store-or-network', idQuery);
|
|
95
|
+
const data = usePreloadedQuery(idQuery, queryRef);
|
|
96
|
+
return data.node ? /*#__PURE__*/React.createElement(FeatureTestersList, _extends({
|
|
97
|
+
collection: data.node
|
|
98
|
+
}, options)) : null;
|
|
99
|
+
}
|
|
100
|
+
export function FeatureTestersPreloadedSlugQueryRenderer(_ref5) {
|
|
101
|
+
var _data$viewer2, _data$viewer2$slug;
|
|
102
|
+
let {
|
|
103
|
+
preloadedQuery,
|
|
104
|
+
...options
|
|
105
|
+
} = _ref5;
|
|
106
|
+
const queryRef = useSerializablePreloadedQuery(preloadedQuery, 'store-or-network', slugQuery);
|
|
107
|
+
const data = usePreloadedQuery(slugQuery, queryRef);
|
|
108
|
+
const coll = (_data$viewer2 = data.viewer) === null || _data$viewer2 === void 0 ? void 0 : (_data$viewer2$slug = _data$viewer2.slug) === null || _data$viewer2$slug === void 0 ? void 0 : _data$viewer2$slug.fontCollection;
|
|
109
|
+
return coll ? /*#__PURE__*/React.createElement(FeatureTestersList, _extends({
|
|
110
|
+
collection: coll
|
|
111
|
+
}, options)) : null;
|
|
112
|
+
}
|
|
113
|
+
function FeatureTestersPreloadedRenderer(_ref6) {
|
|
114
|
+
let {
|
|
115
|
+
preloadedQuery,
|
|
116
|
+
...options
|
|
117
|
+
} = _ref6;
|
|
118
|
+
if (preloadedQuery.params.name === 'FeatureTestersIdQuery') {
|
|
119
|
+
return /*#__PURE__*/React.createElement(FeatureTestersPreloadedIDQueryRenderer, _extends({
|
|
120
|
+
preloadedQuery: preloadedQuery
|
|
121
|
+
}, options));
|
|
122
|
+
}
|
|
123
|
+
return /*#__PURE__*/React.createElement(FeatureTestersPreloadedSlugQueryRenderer, _extends({
|
|
124
|
+
preloadedQuery: preloadedQuery
|
|
125
|
+
}, options));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Renders every Feature Tester card a tenant has configured on a font
|
|
129
|
+
// collection. Accepts a preloaded query (server-side preload path — Astro/RR7
|
|
130
|
+
// frontmatter or a manual loader call) or `{collectionId}`/`{collectionSlug}`
|
|
131
|
+
// for lazy fetch.
|
|
132
|
+
export default function FeatureTesters(_ref7) {
|
|
133
|
+
let {
|
|
134
|
+
config,
|
|
135
|
+
...props
|
|
136
|
+
} = _ref7;
|
|
137
|
+
const {
|
|
138
|
+
highlightColor,
|
|
139
|
+
toggle,
|
|
140
|
+
autofit
|
|
141
|
+
} = props;
|
|
142
|
+
const options = {
|
|
143
|
+
highlightColor,
|
|
144
|
+
toggle,
|
|
145
|
+
autofit
|
|
146
|
+
};
|
|
147
|
+
return /*#__PURE__*/React.createElement(EnsureFontdueContext, {
|
|
148
|
+
config: config
|
|
149
|
+
}, 'preloadedQuery' in props && props.preloadedQuery ? /*#__PURE__*/React.createElement(FeatureTestersPreloadedRenderer, _extends({
|
|
150
|
+
preloadedQuery: props.preloadedQuery
|
|
151
|
+
}, options)) : 'collectionId' in props && props.collectionId ? /*#__PURE__*/React.createElement(ById, _extends({
|
|
152
|
+
collectionId: props.collectionId
|
|
153
|
+
}, options)) : /*#__PURE__*/React.createElement(BySlug, _extends({
|
|
154
|
+
collectionSlug: props.collectionSlug
|
|
155
|
+
}, options)));
|
|
156
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { FeatureTestersProps } from './FeatureTesters.js';
|
|
3
|
+
export type { FeatureTestersPreloadedQuery } from './FeatureTesters.js';
|
|
4
|
+
export declare function loadFeatureTestersQuery(): never;
|
|
5
|
+
export default function FeatureTesters({ collectionId, collectionSlug, ...rest }: Extract<FeatureTestersProps, {
|
|
6
|
+
collectionId: string;
|
|
7
|
+
} | {
|
|
8
|
+
collectionSlug: string;
|
|
9
|
+
}>): Promise<React.JSX.Element | null>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import loadSerializableQuery from '../../relay/loadSerializableQuery.js';
|
|
4
|
+
import { FeatureTestersPreloadedIDQueryRenderer, FeatureTestersPreloadedSlugQueryRenderer } from './FeatureTesters.js';
|
|
5
|
+
import FeatureTestersIdQueryNode from '../../__generated__/FeatureTestersIdQuery.graphql.js';
|
|
6
|
+
import FeatureTestersSlugQueryNode from '../../__generated__/FeatureTestersSlugQuery.graphql.js';
|
|
7
|
+
// Stub for the RSC export condition. See FontdueProvider/index.server.tsx
|
|
8
|
+
// for the rationale.
|
|
9
|
+
export function loadFeatureTestersQuery() {
|
|
10
|
+
throw new Error("loadFeatureTestersQuery isn't needed in React Server Components — " + 'the <FeatureTesters> server entrypoint awaits its query automatically. ' + 'Drop the manual call.');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Next RSC variant: awaits the query on the server and hands the response to
|
|
14
|
+
// the client `*PreloadedQueryRenderer`. Lets pages just write
|
|
15
|
+
// `<FeatureTesters collectionSlug="..." />` in an RSC without manually invoking
|
|
16
|
+
// `loadFeatureTestersQuery` + threading `preloadedQuery` through props.
|
|
17
|
+
export default async function FeatureTesters(_ref) {
|
|
18
|
+
let {
|
|
19
|
+
collectionId,
|
|
20
|
+
collectionSlug,
|
|
21
|
+
...rest
|
|
22
|
+
} = _ref;
|
|
23
|
+
if (collectionId) {
|
|
24
|
+
const preloadedQuery = await loadSerializableQuery(FeatureTestersIdQueryNode, {
|
|
25
|
+
id: collectionId
|
|
26
|
+
});
|
|
27
|
+
return /*#__PURE__*/React.createElement(FeatureTestersPreloadedIDQueryRenderer, _extends({
|
|
28
|
+
preloadedQuery: preloadedQuery
|
|
29
|
+
}, rest));
|
|
30
|
+
}
|
|
31
|
+
if (collectionSlug) {
|
|
32
|
+
const preloadedQuery = await loadSerializableQuery(FeatureTestersSlugQueryNode, {
|
|
33
|
+
slug: collectionSlug
|
|
34
|
+
});
|
|
35
|
+
return /*#__PURE__*/React.createElement(FeatureTestersPreloadedSlugQueryRenderer, _extends({
|
|
36
|
+
preloadedQuery: preloadedQuery
|
|
37
|
+
}, rest));
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface FeatureTestersElement_props {
|
|
3
|
+
collectionId?: string;
|
|
4
|
+
collectionSlug?: string;
|
|
5
|
+
highlightColor?: string;
|
|
6
|
+
toggle?: string;
|
|
7
|
+
autofit?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const FeatureTestersElement: React.FC<FeatureTestersElement_props>;
|
|
10
|
+
export default FeatureTestersElement;
|