@lavalogic/scoria 0.40.17 → 0.40.19
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/Components/AppointmentCalendar/AppointmentCalendar.svelte +87 -10
- package/dist/Components/AppointmentCalendar/AppointmentCalendarProps.d.ts +2 -0
- package/dist/Components/DateInput.svelte +1 -1
- package/dist/Components/DateInputProps.d.ts +2 -1
- package/dist/Components/TimeInputProps.d.ts +51 -0
- package/dist/Components/TimeInputProps.js +1 -0
- package/dist/Types/Internal/Misc.d.ts +2 -0
- package/dist/index.d.ts +2 -1
- package/package.json +1 -1
|
@@ -100,6 +100,15 @@ const timeSlots = $derived.by(() => {
|
|
|
100
100
|
* all) is treated as fully available, so nothing is shaded until the
|
|
101
101
|
* caller supplies windows for that door.
|
|
102
102
|
*/
|
|
103
|
+
function slotUnavailableInWindows(windows, slot) {
|
|
104
|
+
const minutes = slot.hour * 60 + slot.minute;
|
|
105
|
+
for (const window of windows) {
|
|
106
|
+
if (minutes >= window.startMinutes && minutes < window.endMinutes) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
103
112
|
function isSlotUnavailable(doorId, slotIndex) {
|
|
104
113
|
if (!availability) {
|
|
105
114
|
return false;
|
|
@@ -112,13 +121,53 @@ function isSlotUnavailable(doorId, slotIndex) {
|
|
|
112
121
|
if (!slot) {
|
|
113
122
|
return false;
|
|
114
123
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
124
|
+
return slotUnavailableInWindows(windows, slot);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Contiguous unavailable runs per door, expressed as top/height
|
|
128
|
+
* percentages of the day column. Rendered as a single overlay block per
|
|
129
|
+
* run so the shading pattern is continuous across the whole region
|
|
130
|
+
* rather than restarting (and visibly seaming) at every slot cell.
|
|
131
|
+
*/
|
|
132
|
+
const unavailableRunsByDoor = $derived.by(() => {
|
|
133
|
+
// Local index built inside a $derived; outer reactivity covers reads.
|
|
134
|
+
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
|
135
|
+
const byDoor = new Map();
|
|
136
|
+
const slotCount = timeSlots.length;
|
|
137
|
+
if (!availability || slotCount === 0) {
|
|
138
|
+
return byDoor;
|
|
139
|
+
}
|
|
140
|
+
for (const door of doors) {
|
|
141
|
+
const windows = availability.get(door.id);
|
|
142
|
+
if (!windows) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const runs = [];
|
|
146
|
+
let runStart = null;
|
|
147
|
+
// One extra iteration (i === slotCount) flushes a run that reaches
|
|
148
|
+
// the end of the day.
|
|
149
|
+
for (let i = 0; i <= slotCount; i++) {
|
|
150
|
+
const unavailable = i < slotCount && slotUnavailableInWindows(windows, timeSlots[i]);
|
|
151
|
+
if (unavailable) {
|
|
152
|
+
if (runStart === null) {
|
|
153
|
+
runStart = i;
|
|
154
|
+
}
|
|
155
|
+
} else if (runStart !== null) {
|
|
156
|
+
runs.push({
|
|
157
|
+
topPercent: runStart / slotCount * 100,
|
|
158
|
+
heightPercent: (i - runStart) / slotCount * 100
|
|
159
|
+
});
|
|
160
|
+
runStart = null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (runs.length > 0) {
|
|
164
|
+
byDoor.set(door.id, runs);
|
|
119
165
|
}
|
|
120
166
|
}
|
|
121
|
-
return
|
|
167
|
+
return byDoor;
|
|
168
|
+
});
|
|
169
|
+
function unavailableRunsAt(doorId) {
|
|
170
|
+
return unavailableRunsByDoor.get(doorId) ?? [];
|
|
122
171
|
}
|
|
123
172
|
/**
|
|
124
173
|
* Build a `YYYY-MM-DD` key for date bucketing. Avoids `toISOString`
|
|
@@ -696,6 +745,13 @@ $effect(() => {
|
|
|
696
745
|
}}
|
|
697
746
|
></div>
|
|
698
747
|
{/each}
|
|
748
|
+
<!-- Unavailable shading: one continuous block per contiguous run -->
|
|
749
|
+
{#each unavailableRunsAt(door.id) as run (run.topPercent)}
|
|
750
|
+
<div
|
|
751
|
+
class="unavailable-overlay"
|
|
752
|
+
style="top: {run.topPercent}%; height: {run.heightPercent}%;"
|
|
753
|
+
></div>
|
|
754
|
+
{/each}
|
|
699
755
|
<!-- Appointment blocks positioned absolutely (rendered after, sit above slots) -->
|
|
700
756
|
{#each appointmentsAt(door.id, day) as apt (apt.id)}
|
|
701
757
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
@@ -715,6 +771,9 @@ $effect(() => {
|
|
|
715
771
|
{#if apt.sublabel}
|
|
716
772
|
<span class="apt-sublabel">{apt.sublabel}</span>
|
|
717
773
|
{/if}
|
|
774
|
+
{#if apt.reference}
|
|
775
|
+
<span class="apt-reference">Ref: {apt.reference}</span>
|
|
776
|
+
{/if}
|
|
718
777
|
</div>
|
|
719
778
|
{/each}
|
|
720
779
|
</div>
|
|
@@ -939,8 +998,16 @@ $effect(() => {
|
|
|
939
998
|
}
|
|
940
999
|
.slot-cell.unavailable {
|
|
941
1000
|
cursor: not-allowed;
|
|
942
|
-
|
|
943
|
-
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
.unavailable-overlay {
|
|
1004
|
+
position: absolute;
|
|
1005
|
+
left: 0;
|
|
1006
|
+
right: 0;
|
|
1007
|
+
z-index: 3;
|
|
1008
|
+
pointer-events: none;
|
|
1009
|
+
background-color: rgba(128, 150, 165, 0.06);
|
|
1010
|
+
background-image: repeating-linear-gradient(45deg, transparent, transparent 6px, rgba(128, 150, 165, 0.14) 6px, rgba(128, 150, 165, 0.14) 12px);
|
|
944
1011
|
}
|
|
945
1012
|
|
|
946
1013
|
.appointment-block {
|
|
@@ -980,11 +1047,15 @@ $effect(() => {
|
|
|
980
1047
|
color: #259fc7;
|
|
981
1048
|
}
|
|
982
1049
|
.appointment-block.outbound {
|
|
983
|
-
border-color: #
|
|
984
|
-
background-color: #
|
|
1050
|
+
border-color: #66bb6a;
|
|
1051
|
+
background-color: #eaf7ec;
|
|
985
1052
|
}
|
|
986
1053
|
.appointment-block.outbound .apt-type {
|
|
987
|
-
color: #
|
|
1054
|
+
color: #2e7d32;
|
|
1055
|
+
}
|
|
1056
|
+
.appointment-block.outbound:hover {
|
|
1057
|
+
background-color: #dcefdd;
|
|
1058
|
+
border-color: #43a047;
|
|
988
1059
|
}
|
|
989
1060
|
|
|
990
1061
|
.apt-type {
|
|
@@ -1003,6 +1074,12 @@ $effect(() => {
|
|
|
1003
1074
|
font-size: 0.75rem;
|
|
1004
1075
|
}
|
|
1005
1076
|
|
|
1077
|
+
.apt-reference {
|
|
1078
|
+
color: #647d8e;
|
|
1079
|
+
font-size: 0.75rem;
|
|
1080
|
+
font-weight: 600;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1006
1083
|
.week-day-tabs {
|
|
1007
1084
|
display: flex;
|
|
1008
1085
|
flex-flow: row nowrap;
|
|
@@ -29,6 +29,8 @@ export interface CalendarAppointment {
|
|
|
29
29
|
readonly typeCode: string;
|
|
30
30
|
readonly label: string;
|
|
31
31
|
readonly sublabel?: string;
|
|
32
|
+
/** Optional booking reference shown on the appointment card. */
|
|
33
|
+
readonly reference?: string;
|
|
32
34
|
}
|
|
33
35
|
export interface CalendarDoor {
|
|
34
36
|
readonly id: number;
|
|
@@ -82,7 +82,7 @@ function handleChange(e) {
|
|
|
82
82
|
/>
|
|
83
83
|
{/if}
|
|
84
84
|
<input
|
|
85
|
-
step={type === 'datetime-local' ? 1 : 'any'}
|
|
85
|
+
step={type === 'datetime-local' ? 1 : type === 'time' ? 60 : 'any'}
|
|
86
86
|
class:invalid={isInvalid}
|
|
87
87
|
class:left-pad={leftIcon != undefined}
|
|
88
88
|
class:right-pad={rightIcon != undefined}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { DateOnlyInputProps } from './DateOnlyInputProps.js';
|
|
2
2
|
import type { DateTimeInputProps } from './DateTimeInputProps.js';
|
|
3
|
-
|
|
3
|
+
import type { TimeInputProps } from './TimeInputProps.js';
|
|
4
|
+
export type DateInputProps = DateOnlyInputProps | DateTimeInputProps | TimeInputProps;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { InputVariant } from '../Types/Internal/InputVariant.js';
|
|
2
|
+
import type { InputTime } from '../Types/Internal/Misc.js';
|
|
3
|
+
import type { PartialIconProps } from './PartialIconProps.js';
|
|
4
|
+
/**
|
|
5
|
+
* Props for the `time` variant of {@link DateInput}.
|
|
6
|
+
*
|
|
7
|
+
* Renders a native `<input type="time">` with the same overlapping-label
|
|
8
|
+
* border chrome as the date/datetime variants. `value` is branded with
|
|
9
|
+
* {@link InputTime} (`HH:MM`); the empty string is a known sentinel that
|
|
10
|
+
* the consumer is expected to handle when binding.
|
|
11
|
+
*/
|
|
12
|
+
export interface TimeInputProps {
|
|
13
|
+
/** Discriminator. Must be the literal `'time'`. */
|
|
14
|
+
type: 'time';
|
|
15
|
+
/**
|
|
16
|
+
* Bindable time value (`HH:MM`). `''` or `null` represent "no time
|
|
17
|
+
* selected" — both are accepted because consumers commonly back the
|
|
18
|
+
* binding with a nullable field; the component treats them alike.
|
|
19
|
+
*/
|
|
20
|
+
value?: InputTime | '' | null;
|
|
21
|
+
/** Label rendered above the input. */
|
|
22
|
+
label?: string;
|
|
23
|
+
/** Native `placeholder` attribute. */
|
|
24
|
+
placeholder?: string;
|
|
25
|
+
/** Native `required` attribute. */
|
|
26
|
+
required?: boolean;
|
|
27
|
+
/** Render the "- optional" suffix next to `label`. */
|
|
28
|
+
optional?: boolean;
|
|
29
|
+
/** Render variant. See {@link InputVariant}. */
|
|
30
|
+
variant?: InputVariant;
|
|
31
|
+
/** Native `min` constraint. */
|
|
32
|
+
min?: InputTime;
|
|
33
|
+
/** Native `max` constraint. */
|
|
34
|
+
max?: InputTime;
|
|
35
|
+
/** Native `disabled` attribute. */
|
|
36
|
+
disabled?: boolean;
|
|
37
|
+
/** Icon rendered on the left of the input. */
|
|
38
|
+
leftIcon?: PartialIconProps;
|
|
39
|
+
/** Icon rendered on the right of the input. */
|
|
40
|
+
rightIcon?: PartialIconProps;
|
|
41
|
+
/** Bindable reference to the underlying `<input>` element. */
|
|
42
|
+
inputRef?: HTMLInputElement;
|
|
43
|
+
/** Force the invalid visual state. */
|
|
44
|
+
invalid?: boolean;
|
|
45
|
+
/** Blur handler. */
|
|
46
|
+
onblur?: (e: FocusEvent) => void;
|
|
47
|
+
/** Focus handler. */
|
|
48
|
+
onfocus?: (e: FocusEvent) => void;
|
|
49
|
+
/** Change handler. May return `void` or `Promise<void>`. */
|
|
50
|
+
onchange?: (value: InputTime | null) => void | Promise<void>;
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -69,6 +69,8 @@ export type ScoriaStorageKey = BrandedString<'ScoriaStorageKey'>;
|
|
|
69
69
|
export type InputDate = BrandedString<'InputDate'>;
|
|
70
70
|
/** ISO date-time string of the form `YYYY-MM-DDTHH:MM:SS`. */
|
|
71
71
|
export type InputDateWithTime = BrandedString<'InputDateWithTime'>;
|
|
72
|
+
/** ISO time-only string of the form `HH:MM`. */
|
|
73
|
+
export type InputTime = BrandedString<'InputTime'>;
|
|
72
74
|
/** Date string formatted for the FloWMS SQL backend. */
|
|
73
75
|
export type SQLDate = BrandedString<'SQLDate'>;
|
|
74
76
|
/** Integer centimetre count. Used by dimension inputs. */
|
package/dist/index.d.ts
CHANGED
|
@@ -41,6 +41,7 @@ export type { DateOnlyInputProps } from './Components/DateOnlyInputProps.js';
|
|
|
41
41
|
export { default as DatePicker } from './Components/DatePicker.svelte';
|
|
42
42
|
export { type DatePickerProps } from './Components/DatePickerProps.js';
|
|
43
43
|
export type { DateTimeInputProps } from './Components/DateTimeInputProps.js';
|
|
44
|
+
export type { TimeInputProps } from './Components/TimeInputProps.js';
|
|
44
45
|
export { default as DesktopModal } from './Components/DesktopModal.svelte';
|
|
45
46
|
export type { DesktopModalProps } from './Components/DesktopModalProps.js';
|
|
46
47
|
export { default as Dial } from './Components/Dial.svelte';
|
|
@@ -210,7 +211,7 @@ export { type GridStringItem } from './Types/Internal/GridStringItem.js';
|
|
|
210
211
|
export { type GridTextItem } from './Types/Internal/GridTextItem.js';
|
|
211
212
|
export { type IdentifiableToastMessage } from './Types/Internal/IdentifiableToastMessage.js';
|
|
212
213
|
export { InputVariant } from './Types/Internal/InputVariant.js';
|
|
213
|
-
export { type BrandedInteger, type BrandedNumber, type BrandedString, type Centimetres, type CssLength, type InputDate, type InputDateWithTime, type NonNullableKey, type Pixels, type ScoriaStorageKey, type SQLDate, type Unsubscriber, type Uuid, type ValueProp, } from './Types/Internal/Misc.js';
|
|
214
|
+
export { type BrandedInteger, type BrandedNumber, type BrandedString, type Centimetres, type CssLength, type InputDate, type InputDateWithTime, type InputTime, type NonNullableKey, type Pixels, type ScoriaStorageKey, type SQLDate, type Unsubscriber, type Uuid, type ValueProp, } from './Types/Internal/Misc.js';
|
|
214
215
|
export { type RefWrapper } from './Types/Internal/RefWrapper.js';
|
|
215
216
|
export { type SelectOption } from './Types/Internal/SelectOption.js';
|
|
216
217
|
export { Shape } from './Types/Internal/Shape.js';
|