@nomideusz/svelte-calendar 0.10.0 → 0.12.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.
@@ -1,104 +0,0 @@
1
- /**
2
- * Text measurement utilities powered by Pretext.
3
- *
4
- * Provides DOM-free text measurement for calendar event blocks.
5
- * Pretext is an optional peer dependency — if not installed,
6
- * these functions degrade gracefully to simple heuristics.
7
- *
8
- * Usage:
9
- * const m = createTextMeasure({ font: '500 12px Inter' });
10
- * const fits = m.fits('Meeting with team', 120, 16); // width, lineHeight
11
- * const { height, lineCount } = m.measure('Long event title...', 120, 16);
12
- * m.clear(); // release caches
13
- */
14
- export interface TextMeasure {
15
- /**
16
- * Measure text height at a given width.
17
- * Returns { height, lineCount }.
18
- */
19
- measure(text: string, maxWidth: number, lineHeight: number): {
20
- height: number;
21
- lineCount: number;
22
- };
23
- /**
24
- * Check if text fits in a single line at the given width.
25
- */
26
- fits(text: string, maxWidth: number, lineHeight: number): boolean;
27
- /**
28
- * Measure multiple texts and return their combined height.
29
- * Useful for checking if title + subtitle + tags fit in a block.
30
- */
31
- measureStack(items: Array<{
32
- text: string;
33
- font?: string;
34
- lineHeight?: number;
35
- }>, maxWidth: number, gap?: number): {
36
- height: number;
37
- breakdown: Array<{
38
- height: number;
39
- lineCount: number;
40
- }>;
41
- };
42
- /**
43
- * Given available height and width, decide which content layers fit.
44
- * Returns a visibility map for common event fields.
45
- */
46
- fitContent(opts: {
47
- title: string;
48
- subtitle?: string;
49
- location?: string;
50
- time?: string;
51
- tags?: string[];
52
- maxWidth: number;
53
- maxHeight: number;
54
- }): ContentFit;
55
- /** Release internal caches. */
56
- clear(): void;
57
- /** Whether Pretext is available (true) or using heuristic fallback (false). */
58
- readonly available: boolean;
59
- }
60
- export interface ContentFit {
61
- /** Title always shown */
62
- title: true;
63
- /** Number of lines the title takes */
64
- titleLines: number;
65
- /** Whether subtitle fits */
66
- subtitle: boolean;
67
- /** Whether location fits */
68
- location: boolean;
69
- /** Whether time range fits */
70
- time: boolean;
71
- /** Whether tags fit */
72
- tags: boolean;
73
- /** Total height of all visible content */
74
- totalHeight: number;
75
- }
76
- export interface TextMeasureOptions {
77
- /** CSS font shorthand for titles (e.g. '500 12px Inter') */
78
- titleFont?: string;
79
- /** CSS font shorthand for secondary text (subtitle, location, time) */
80
- secondaryFont?: string;
81
- /** CSS font shorthand for tags */
82
- tagFont?: string;
83
- /** Default line height for titles */
84
- titleLineHeight?: number;
85
- /** Default line height for secondary text */
86
- secondaryLineHeight?: number;
87
- /** Gap between content rows in px */
88
- contentGap?: number;
89
- }
90
- /**
91
- * Create a text measure instance.
92
- * Lazily loads Pretext on first use. Falls back to char-width heuristics if unavailable.
93
- */
94
- export declare function createTextMeasure(opts?: TextMeasureOptions): TextMeasure;
95
- /**
96
- * Initialize Pretext for text measurement.
97
- * Call this once in your app's entry point (e.g., +layout.svelte onMount).
98
- * If not called, measurement falls back to character-width heuristics.
99
- *
100
- * Usage:
101
- * import { initTextMeasure } from '@nomideusz/svelte-calendar';
102
- * onMount(() => initTextMeasure());
103
- */
104
- export declare function initTextMeasure(): Promise<boolean>;
@@ -1,195 +0,0 @@
1
- /**
2
- * Text measurement utilities powered by Pretext.
3
- *
4
- * Provides DOM-free text measurement for calendar event blocks.
5
- * Pretext is an optional peer dependency — if not installed,
6
- * these functions degrade gracefully to simple heuristics.
7
- *
8
- * Usage:
9
- * const m = createTextMeasure({ font: '500 12px Inter' });
10
- * const fits = m.fits('Meeting with team', 120, 16); // width, lineHeight
11
- * const { height, lineCount } = m.measure('Long event title...', 120, 16);
12
- * m.clear(); // release caches
13
- */
14
- const DEFAULTS = {
15
- titleFont: '500 12px system-ui, sans-serif',
16
- secondaryFont: '400 10px system-ui, sans-serif',
17
- tagFont: '500 8px system-ui, sans-serif',
18
- titleLineHeight: 16,
19
- secondaryLineHeight: 13,
20
- contentGap: 3,
21
- };
22
- /**
23
- * Create a text measure instance.
24
- * Lazily loads Pretext on first use. Falls back to char-width heuristics if unavailable.
25
- */
26
- export function createTextMeasure(opts = {}) {
27
- const config = { ...DEFAULTS, ...opts };
28
- // Pretext module — loaded lazily
29
- let pretext = null;
30
- let loadAttempted = false;
31
- let pretextAvailable = false;
32
- // Cache prepared texts (font+text → prepared handle)
33
- const cache = new Map();
34
- function tryLoadPretext() {
35
- if (loadAttempted)
36
- return pretextAvailable;
37
- loadAttempted = true;
38
- try {
39
- // Dynamic require/import for optional dependency
40
- // This works because Pretext is a synchronous API
41
- const mod = globalThis.__pretextModule;
42
- if (mod) {
43
- pretext = mod;
44
- pretextAvailable = true;
45
- }
46
- }
47
- catch {
48
- // Not available — that's fine
49
- }
50
- return pretextAvailable;
51
- }
52
- function getPrepared(text, font) {
53
- const key = `${font}\0${text}`;
54
- let prepared = cache.get(key);
55
- if (!prepared) {
56
- prepared = pretext.prepare(text, font);
57
- cache.set(key, prepared);
58
- }
59
- return prepared;
60
- }
61
- // Heuristic fallback: estimate chars per line from average char width
62
- function heuristicMeasure(text, maxWidth, lineHeight, font) {
63
- // Rough average char widths by font size
64
- const sizeMatch = font.match(/(\d+)px/);
65
- const fontSize = sizeMatch ? parseInt(sizeMatch[1]) : 12;
66
- const avgCharWidth = fontSize * 0.55; // rough average for Latin text
67
- const charsPerLine = Math.max(1, Math.floor(maxWidth / avgCharWidth));
68
- const lineCount = Math.max(1, Math.ceil(text.length / charsPerLine));
69
- return { height: lineCount * lineHeight, lineCount };
70
- }
71
- function measureOne(text, maxWidth, lineHeight, font) {
72
- if (!text)
73
- return { height: 0, lineCount: 0 };
74
- if (pretextAvailable && pretext) {
75
- const prepared = getPrepared(text, font);
76
- return pretext.layout(prepared, maxWidth, lineHeight);
77
- }
78
- return heuristicMeasure(text, maxWidth, lineHeight, font);
79
- }
80
- return {
81
- get available() {
82
- tryLoadPretext();
83
- return pretextAvailable;
84
- },
85
- measure(text, maxWidth, lineHeight) {
86
- tryLoadPretext();
87
- return measureOne(text, maxWidth, lineHeight, config.titleFont);
88
- },
89
- fits(text, maxWidth, lineHeight) {
90
- tryLoadPretext();
91
- const { lineCount } = measureOne(text, maxWidth, lineHeight, config.titleFont);
92
- return lineCount <= 1;
93
- },
94
- measureStack(items, maxWidth, gap = config.contentGap) {
95
- tryLoadPretext();
96
- const breakdown = [];
97
- let totalHeight = 0;
98
- for (const item of items) {
99
- if (!item.text) {
100
- breakdown.push({ height: 0, lineCount: 0 });
101
- continue;
102
- }
103
- const font = item.font ?? config.secondaryFont;
104
- const lh = item.lineHeight ?? config.secondaryLineHeight;
105
- const result = measureOne(item.text, maxWidth, lh, font);
106
- breakdown.push(result);
107
- if (result.height > 0) {
108
- totalHeight += (totalHeight > 0 ? gap : 0) + result.height;
109
- }
110
- }
111
- return { height: totalHeight, breakdown };
112
- },
113
- fitContent(opts) {
114
- tryLoadPretext();
115
- const { title, subtitle, location, time, tags, maxWidth, maxHeight } = opts;
116
- const gap = config.contentGap;
117
- // Title always measured
118
- const titleResult = measureOne(title, maxWidth, config.titleLineHeight, config.titleFont);
119
- let used = titleResult.height;
120
- const result = {
121
- title: true,
122
- titleLines: titleResult.lineCount,
123
- subtitle: false,
124
- location: false,
125
- time: false,
126
- tags: false,
127
- totalHeight: used,
128
- };
129
- // Try adding time
130
- if (time) {
131
- const h = measureOne(time, maxWidth, config.secondaryLineHeight, config.secondaryFont);
132
- if (used + gap + h.height <= maxHeight) {
133
- result.time = true;
134
- used += gap + h.height;
135
- }
136
- }
137
- // Try adding subtitle
138
- if (subtitle) {
139
- const h = measureOne(subtitle, maxWidth, config.secondaryLineHeight, config.secondaryFont);
140
- if (used + gap + h.height <= maxHeight) {
141
- result.subtitle = true;
142
- used += gap + h.height;
143
- }
144
- }
145
- // Try adding location
146
- if (location) {
147
- const h = measureOne(location, maxWidth, config.secondaryLineHeight, config.secondaryFont);
148
- if (used + gap + h.height <= maxHeight) {
149
- result.location = true;
150
- used += gap + h.height;
151
- }
152
- }
153
- // Try adding tags
154
- if (tags?.length) {
155
- const tagText = tags.join(' ');
156
- const h = measureOne(tagText, maxWidth, config.secondaryLineHeight, config.tagFont);
157
- if (used + gap + h.height <= maxHeight) {
158
- result.tags = true;
159
- used += gap + h.height;
160
- }
161
- }
162
- result.totalHeight = used;
163
- return result;
164
- },
165
- clear() {
166
- cache.clear();
167
- if (pretextAvailable && pretext) {
168
- pretext.clearCache();
169
- }
170
- },
171
- };
172
- }
173
- /**
174
- * Initialize Pretext for text measurement.
175
- * Call this once in your app's entry point (e.g., +layout.svelte onMount).
176
- * If not called, measurement falls back to character-width heuristics.
177
- *
178
- * Usage:
179
- * import { initTextMeasure } from '@nomideusz/svelte-calendar';
180
- * onMount(() => initTextMeasure());
181
- */
182
- export async function initTextMeasure() {
183
- try {
184
- const mod = await import('@chenglou/pretext');
185
- globalThis.__pretextModule = {
186
- prepare: mod.prepare,
187
- layout: mod.layout,
188
- clearCache: mod.clearCache,
189
- };
190
- return true;
191
- }
192
- catch {
193
- return false;
194
- }
195
- }