@nomideusz/svelte-calendar 1.8.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -1
- package/dist/text-fit.d.ts +4 -0
- package/dist/text-fit.js +24 -5
- package/package.json +1 -1
- package/widget/widget.js +27 -3
package/README.md
CHANGED
|
@@ -1112,7 +1112,7 @@ The widget renders inside shadow DOM: host-page CSS cannot affect the calendar a
|
|
|
1112
1112
|
| `api` | — | Events endpoint, fetched as `GET {api}?start=…&end=…` (ISO instants; appended with `&` if `api` already has a query). It must answer JSON: an array of events, or `{ "events": [...] }`. |
|
|
1113
1113
|
| `events` | — | Inline JSON array of events, used when there is no `api` |
|
|
1114
1114
|
| `headers` | — | JSON object of extra request headers for `api`. Without it the GET is a simple CORS request (no preflight). |
|
|
1115
|
-
| `readonly` | read-only | The widget is read-only unless `readonly="false"`. There is no write endpoint: with `api`, drags are only visual; with `events`, they are kept in memory. |
|
|
1115
|
+
| `readonly` | read-only | The widget is read-only unless `readonly="false"`. There is no write endpoint: with `api`, drags are only visual; with `events`, they are kept in memory. Either way each committed drag fires `eventmove`. |
|
|
1116
1116
|
| `view` | `week-planner` | Initial view ID (`week-planner`, `day-agenda`, `month-grid`, …) |
|
|
1117
1117
|
| `theme` | `auto` | `auto`, `neutral` or `midnight` (unknown names fall back to `neutral`) |
|
|
1118
1118
|
| `height` | `600` | Height in px (`"600"` or `"600px"`), or `auto` |
|
|
@@ -1131,6 +1131,21 @@ The widget renders inside shadow DOM: host-page CSS cannot affect the calendar a
|
|
|
1131
1131
|
|
|
1132
1132
|
Each event in `api` responses and `events` needs `start` and `end` as date strings (ISO 8601) and may carry `id`, `title`, `color`, `allDay`, `subtitle` and `location`; other fields are ignored, as are events with an invalid or empty range. Changing an attribute updates the calendar in place.
|
|
1133
1133
|
|
|
1134
|
+
The element fires two DOM events (they bubble), with the event as the calendar holds it (`start` and `end` as `Date`s):
|
|
1135
|
+
|
|
1136
|
+
| Event | `detail` | When |
|
|
1137
|
+
|-------|----------|------|
|
|
1138
|
+
| `eventclick` | `{ event, anchor }` | An event is clicked or activated with Enter; `anchor` is its block's viewport rect |
|
|
1139
|
+
| `eventmove` | `{ event, start, end }` | A drag or resize committed (only with `readonly="false"`); `event` is the one before the move |
|
|
1140
|
+
|
|
1141
|
+
```js
|
|
1142
|
+
const cal = document.querySelector('day-calendar');
|
|
1143
|
+
cal.addEventListener('eventclick', (e) => open(e.detail.event.id));
|
|
1144
|
+
cal.addEventListener('eventmove', (e) => save(e.detail.event.id, e.detail.start, e.detail.end));
|
|
1145
|
+
```
|
|
1146
|
+
|
|
1147
|
+
Keep your own copy in step with `eventmove` if you later set `events` again: a new `events` value replaces what the calendar holds.
|
|
1148
|
+
|
|
1134
1149
|
## All Props
|
|
1135
1150
|
|
|
1136
1151
|
Every `<Calendar>` prop (`CalendarProps`), grouped by what it controls.
|
package/dist/text-fit.d.ts
CHANGED
|
@@ -50,10 +50,14 @@ export declare function fontOf(el: Element): string;
|
|
|
50
50
|
* that does not come from its own text (block, or a flex item with min-width: 0).
|
|
51
51
|
*/
|
|
52
52
|
export declare function fitLabel(candidates: readonly string[], lines?: number): Attachment<HTMLElement>;
|
|
53
|
+
/** The badness of the loosest line but the last, were `lines` justified at `width`. */
|
|
54
|
+
export declare function looseness(lines: readonly string[], font: string, width: number): number;
|
|
53
55
|
export declare function breakLines(text: string, font: string, width: number): string[];
|
|
54
56
|
/**
|
|
55
57
|
* `<p {@attach typeset(text)}>` — sets the paragraph as justified Knuth-Plass
|
|
56
58
|
* lines, one block span per line, re-done on resize and after webfonts load.
|
|
59
|
+
* A paragraph that cannot be justified without gaps (`TOLERANCE`) goes back to
|
|
60
|
+
* the browser as plain text, set however the element sets it (ragged).
|
|
57
61
|
* Progressive: the server-rendered text is what crawlers and no-JS get; if the
|
|
58
62
|
* browser's own measure disagrees with ours (a line overflows), the plain
|
|
59
63
|
* text goes back and the browser breaks it. Pass the text, not the DOM — the
|
package/dist/text-fit.js
CHANGED
|
@@ -96,6 +96,23 @@ export function fitLabel(candidates, lines = 1) {
|
|
|
96
96
|
// (10 + badness)² plus 50² for ending inside a word, plus 10000 when the line
|
|
97
97
|
// above did too. ponytail: no fitness classes, no shrink; O(n·k).
|
|
98
98
|
const SHY = '';
|
|
99
|
+
const badness = (slackPx, spaces, space) => {
|
|
100
|
+
const r = slackPx / (Math.max(spaces, 1) * space * 0.5);
|
|
101
|
+
return Math.min(10000, 100 * r * r * r);
|
|
102
|
+
};
|
|
103
|
+
// TeX's default \tolerance. A narrow column (a phone) sometimes has no set of
|
|
104
|
+
// breaks that keeps every space under ~1.6× its width; justified, those lines
|
|
105
|
+
// read as holes, so typeset leaves that paragraph to the browser, ragged.
|
|
106
|
+
const TOLERANCE = 200;
|
|
107
|
+
/** The badness of the loosest line but the last, were `lines` justified at `width`. */
|
|
108
|
+
export function looseness(lines, font, width) {
|
|
109
|
+
const measure = (s) => measureNaturalWidth(prepareWithSegments(s, font));
|
|
110
|
+
const space = measure('a a') - measure('aa');
|
|
111
|
+
let worst = 0;
|
|
112
|
+
for (const l of lines.slice(0, -1))
|
|
113
|
+
worst = Math.max(worst, badness(width - measure(l), l.split(' ').length - 1, space));
|
|
114
|
+
return worst;
|
|
115
|
+
}
|
|
99
116
|
export function breakLines(text, font, width) {
|
|
100
117
|
const measure = (s) => measureNaturalWidth(prepareWithSegments(s, font));
|
|
101
118
|
const boxes = [];
|
|
@@ -119,10 +136,6 @@ export function breakLines(text, font, width) {
|
|
|
119
136
|
const bw = boxes.map(measure);
|
|
120
137
|
const space = measure('a a') - measure('aa');
|
|
121
138
|
const hyphen = measure('-');
|
|
122
|
-
const badness = (slackPx, spaces) => {
|
|
123
|
-
const r = slackPx / (Math.max(spaces, 1) * space * 0.5);
|
|
124
|
-
return Math.min(10000, 100 * r * r * r);
|
|
125
|
-
};
|
|
126
139
|
// best[h][k]: cheapest way to set boxes[0..k) with a break after, h = 1 when
|
|
127
140
|
// that last line ended inside a word; prev[h][k]: [start of that line, its h].
|
|
128
141
|
const best = [new Array(n + 1).fill(Infinity), new Array(n + 1).fill(Infinity)];
|
|
@@ -144,7 +157,7 @@ export function breakLines(text, font, width) {
|
|
|
144
157
|
break;
|
|
145
158
|
continue; // only the hyphen overflows — try the next box
|
|
146
159
|
}
|
|
147
|
-
const b = last ? 0 : badness(width - lineW, spaces);
|
|
160
|
+
const b = last ? 0 : badness(width - lineW, spaces, space);
|
|
148
161
|
const cost = last ? 0 : (10 + b) ** 2 + (h ? 50 ** 2 : 0) + (h && ph ? 10000 : 0);
|
|
149
162
|
if (best[ph][i] + cost < best[h][j + 1]) {
|
|
150
163
|
best[h][j + 1] = best[ph][i] + cost;
|
|
@@ -177,6 +190,8 @@ export function breakLines(text, font, width) {
|
|
|
177
190
|
/**
|
|
178
191
|
* `<p {@attach typeset(text)}>` — sets the paragraph as justified Knuth-Plass
|
|
179
192
|
* lines, one block span per line, re-done on resize and after webfonts load.
|
|
193
|
+
* A paragraph that cannot be justified without gaps (`TOLERANCE`) goes back to
|
|
194
|
+
* the browser as plain text, set however the element sets it (ragged).
|
|
180
195
|
* Progressive: the server-rendered text is what crawlers and no-JS get; if the
|
|
181
196
|
* browser's own measure disagrees with ours (a line overflows), the plain
|
|
182
197
|
* text goes back and the browser breaks it. Pass the text, not the DOM — the
|
|
@@ -204,6 +219,10 @@ export function typeset(text) {
|
|
|
204
219
|
'position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap';
|
|
205
220
|
const set = (w) => {
|
|
206
221
|
const lines = breakLines(text, font, w);
|
|
222
|
+
if (looseness(lines, font, w) > TOLERANCE) {
|
|
223
|
+
el.textContent = text;
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
207
226
|
const spans = lines.map((l, i) => {
|
|
208
227
|
const s = document.createElement('span');
|
|
209
228
|
s.setAttribute('aria-hidden', 'true');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nomideusz/svelte-calendar",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "A themeable Svelte 5 calendar: day, week, scrolling-week and month views, agenda lists, touch-first mobile layouts, drag-and-drop, timezones, a headless API and an embeddable web component.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
package/widget/widget.js
CHANGED
|
@@ -20756,6 +20756,7 @@ createHTML: (html) => {
|
|
|
20756
20756
|
/** Minimum day-column width in px for the week grid (default: 110) — a narrower container scrolls sideways */
|
|
20757
20757
|
/** Visible hours of the time grids, "start-end" in whole hours (e.g. "6-22") */
|
|
20758
20758
|
/** UI words as a JSON object over the English defaults, e.g. '{"today":"Dziś"}' */
|
|
20759
|
+
/** Set by the <day-calendar> element: fires its `eventclick` / `eventmove` DOM events */
|
|
20759
20760
|
let theme = prop($$props, "theme", 3, "auto"), view = prop($$props, "view", 3, "week-planner"), height = prop($$props, "height", 3, "600"), mondaystart = prop($$props, "mondaystart", 3, "true");
|
|
20760
20761
|
const heightValue = /* @__PURE__ */ user_derived(() => {
|
|
20761
20762
|
const trimmed = height().trim();
|
|
@@ -20914,7 +20915,16 @@ createHTML: (html) => {
|
|
|
20914
20915
|
},
|
|
20915
20916
|
get labels() {
|
|
20916
20917
|
return get(labelsValue);
|
|
20917
|
-
}
|
|
20918
|
+
},
|
|
20919
|
+
oneventclick: (event, anchor) => $$props.emit?.("eventclick", {
|
|
20920
|
+
event,
|
|
20921
|
+
anchor
|
|
20922
|
+
}),
|
|
20923
|
+
oneventmove: (event, start, end) => $$props.emit?.("eventmove", {
|
|
20924
|
+
event,
|
|
20925
|
+
start,
|
|
20926
|
+
end
|
|
20927
|
+
})
|
|
20918
20928
|
});
|
|
20919
20929
|
}
|
|
20920
20930
|
pop();
|
|
@@ -21030,9 +21040,13 @@ createHTML: (html) => {
|
|
|
21030
21040
|
this.stylesInjected = true;
|
|
21031
21041
|
}
|
|
21032
21042
|
const attrs = createReactiveProps(this.readProps());
|
|
21043
|
+
const emit = (type, detail) => this.dispatchEvent(new CustomEvent(type, {
|
|
21044
|
+
detail,
|
|
21045
|
+
bubbles: true
|
|
21046
|
+
}));
|
|
21033
21047
|
const component = mount(CalendarWidget, {
|
|
21034
21048
|
target: root,
|
|
21035
|
-
props: attrs.props
|
|
21049
|
+
props: Object.assign(attrs.props, { emit })
|
|
21036
21050
|
});
|
|
21037
21051
|
this.instance = {
|
|
21038
21052
|
component,
|
|
@@ -21056,5 +21070,15 @@ createHTML: (html) => {
|
|
|
21056
21070
|
return props;
|
|
21057
21071
|
}
|
|
21058
21072
|
};
|
|
21059
|
-
if (!customElements.get("day-calendar"))
|
|
21073
|
+
if (!customElements.get("day-calendar")) {
|
|
21074
|
+
customElements.define("day-calendar", DayCalendarElement);
|
|
21075
|
+
const registry = customElements;
|
|
21076
|
+
for (const el of document.querySelectorAll("day-calendar")) {
|
|
21077
|
+
if (el instanceof DayCalendarElement) continue;
|
|
21078
|
+
try {
|
|
21079
|
+
registry.initialize?.(el);
|
|
21080
|
+
} catch {}
|
|
21081
|
+
customElements.upgrade(el);
|
|
21082
|
+
}
|
|
21083
|
+
}
|
|
21060
21084
|
})();
|