aboutsummaryrefslogtreecommitdiff
path: root/static/ts/components
diff options
context:
space:
mode:
Diffstat (limited to 'static/ts/components')
-rw-r--r--static/ts/components/changelog.ts39
-rw-r--r--static/ts/components/date-jump.ts39
-rw-r--r--static/ts/components/date-time-input.ts38
-rw-r--r--static/ts/components/input-list.ts70
-rw-r--r--static/ts/components/popup-element.ts6
-rw-r--r--static/ts/components/slider.ts70
-rw-r--r--static/ts/components/tab-group-element.ts9
-rw-r--r--static/ts/components/vevent-block.ts12
-rw-r--r--static/ts/components/vevent.ts19
9 files changed, 237 insertions, 65 deletions
diff --git a/static/ts/components/changelog.ts b/static/ts/components/changelog.ts
index 720d1656..8f8adc1c 100644
--- a/static/ts/components/changelog.ts
+++ b/static/ts/components/changelog.ts
@@ -1,32 +1,43 @@
/**
- * `<changelog />`
- *
- * Display of a VEvents changelog. @ref{ChangeLogEntry}
- *
- * @privateRemarks @anchor{VEventChangelog}
- *
- * @category Web Components
- * @mergeTarget components
- * @module
- */
+ `<changelog />`
+
+ Display of a VEvents changelog. @ref{ChangeLogEntry}
+
+ TODO rename this file!
+
+
+ @privateRemarks @anchor{VEventChangelog}
+
+ @category Web Components
+ @mergeTarget components
+ @module
+*/
import { makeElement } from '../lib'
import { ComponentVEvent } from './vevent'
import { VEvent } from '../vevent'
export { VEventChangelog }
+/**
+ Component displaying veevents changelog.
+
+ This component is dumb, and (almost) doesn't keep any internal state. Instead
+ other parts of the program should call it with a `VEvent`, which contains the
+ actual changelog.
+*/
class VEventChangelog extends ComponentVEvent {
- readonly ul: HTMLElement
+ /** The list holding the changelog */
+ readonly #ul: HTMLElement
constructor(uid?: string) {
super(uid);
- this.ul = makeElement('ul');
+ this.#ul = makeElement('ul');
}
connectedCallback() {
- this.replaceChildren(this.ul);
+ this.replaceChildren(this.#ul);
}
redraw(data: VEvent) {
@@ -55,6 +66,6 @@ class VEventChangelog extends ComponentVEvent {
children.push(makeElement('li', { textContent: msg }));
}
- this.ul.replaceChildren(...children)
+ this.#ul.replaceChildren(...children)
}
}
diff --git a/static/ts/components/date-jump.ts b/static/ts/components/date-jump.ts
index fd3908ae..f1cfe7e6 100644
--- a/static/ts/components/date-jump.ts
+++ b/static/ts/components/date-jump.ts
@@ -1,40 +1,51 @@
+/**
+ `<date-jump />`
+
+ @category Web Components
+ @mergeTarget components
+ @module
+*/
+
export { DateJump }
-/* Replace backend-driven [today] link with frontend, with one that
+/** Replace backend-driven [today] link with frontend, with one that
gets correctly set in the frontend. Similarly, update the go to
specific date button into a link which updates wheneven the date
form updates.
+
+ TODO is this comment correct? We somehow contain an input element also.
*/
class DateJump extends HTMLElement {
- readonly golink: HTMLAnchorElement;
- readonly input: HTMLInputElement;
+ readonly #golink: HTMLAnchorElement;
+ readonly #input: HTMLInputElement;
constructor() {
super();
- this.golink = document.createElement('a')
- this.golink.classList.add('btn');
- this.golink.textContent = "➔"
- this.input = document.createElement('input')
- this.input.type = 'date';
+ this.#golink = document.createElement('a')
+ this.#golink.classList.add('btn');
+ this.#golink.textContent = "➔"
+ this.#input = document.createElement('input')
+ this.#input.type = 'date';
}
+ /** Sets the link to NOW upon mounting */
connectedCallback() {
/* Form is just here so the css works out */
let form = document.createElement('form');
- form.replaceChildren(this.input, this.golink);
+ form.replaceChildren(this.#input, this.#golink);
this.replaceChildren(form);
- this.input.onchange = () => {
- let date = this.input.valueAsDate!.format('~Y-~m-~d');
- this.golink.href = `${date}.html`
+ this.#input.onchange = () => {
+ let date = this.#input.valueAsDate!.format('~Y-~m-~d');
+ this.#golink.href = `${date}.html`
}
let now = (new Date).format("~Y-~m-~d")
- this.input.value = now;
+ this.#input.value = now;
/* onchange isn't triggered by manually setting the value */
- this.golink.href = `${now}.html`
+ this.#golink.href = `${now}.html`
}
}
diff --git a/static/ts/components/date-time-input.ts b/static/ts/components/date-time-input.ts
index d1ab5ba1..33201653 100644
--- a/static/ts/components/date-time-input.ts
+++ b/static/ts/components/date-time-input.ts
@@ -27,7 +27,9 @@ import { makeElement, parseDate } from '../lib'
*/
class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
+ /** Our time input element */
readonly time: HTMLInputElement;
+ /** Our date input element */
readonly date: HTMLInputElement;
constructor() {
@@ -43,22 +45,30 @@ class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
}) as HTMLInputElement
}
+ /**
+ We set our children first when mounted.
+
+ This can be in the constructor for chromium, but NOT firefox...
+
+ - Vivaldi 4.3.2439.63 stable
+ - Mozilla Firefox 94.0.1
+ */
connectedCallback() {
- /* This can be in the constructor for chromium, but NOT firefox...
- Vivaldi 4.3.2439.63 stable
- Mozilla Firefox 94.0.1
- */
- /*
- https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes#boolean_attributes
- https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute
- */
this.replaceChildren(this.date, this.time)
}
+ /**
+ Attributes which we want notifications when they are change.
+
+ Part of the Web Component API
+
+ - `dateonly`
+ */
static get observedAttributes() {
return ['dateonly']
}
+ /** Part of the Web Component API */
attributeChangedCallback(name: string, _: string | null, to: string | null): void {
switch (name) {
case 'dateonly':
@@ -84,6 +94,7 @@ class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
return this.hasAttribute('dateonly');
}
+ /** See getter */
set dateonly(b: boolean) {
if (b) {
this.setAttribute('dateonly', "");
@@ -92,6 +103,7 @@ class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
}
}
+ /** See getter */
set value(date: Date) {
let [d, t] = date.format("~L~Y-~m-~dT~H:~M").split('T');
this.date.value = d;
@@ -124,6 +136,13 @@ class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
}
}
+ /**
+ Set the selected date.
+
+ @param new_value
+ If given a date, set the input to that date.
+ If given a string, parse it as an ISO-8601 formatted datetime.
+ */
set stringValue(new_value: Date | string) {
let date, time, dateonly = false;
if (new_value instanceof Date) {
@@ -138,6 +157,9 @@ class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
this.time.value = time;
}
+ /**
+ Adds an event listener to both the date and time input.
+ */
addEventListener(type: string, proc: ((e: Event) => void)) {
if (type != 'input') throw "Only input supported";
diff --git a/static/ts/components/input-list.ts b/static/ts/components/input-list.ts
index 31dd5158..72d27cab 100644
--- a/static/ts/components/input-list.ts
+++ b/static/ts/components/input-list.ts
@@ -14,26 +14,62 @@ export { InputList }
TODO allow each item to be a larger unit, possibly containing multiple input
fields.
*/
+/**
+ A multi-valued input, done by creating extra input fields as needed.
+
+ The first element of body MUST be an input element, which will be used as the
+ template for each instance. A tag input could for example look like
+
+ @example
+ ```html
+ <input-list name="tags">
+ <input type="text" placeholder="tag ..." />
+ </input-list>
+ ```
+
+ Whenever one of the input elements `value` becomes the empty string, that tag
+ is removed, and whenever there is no element with the empty string as a
+ `value`, a new input element will be added onto the end.
+ */
class InputList extends HTMLElement {
- el: HTMLInputElement;
+ /** The element used as our template. Will be sourced from the initial HTML code. */
+ #el: HTMLInputElement;
+ /**
+ Registered listeners, which will be added onto each created entry
+
+ Keys are event names ('input', 'change', ...) and values event handlers.
+
+ This is a list of tuples rather than a dictionary, since multiple
+ listeners of the same type can be registered.
+ */
#listeners: [string, (e: Event) => void][] = [];
constructor() {
super();
- this.el = this.children[0].cloneNode(true) as HTMLInputElement;
+ this.#el = this.children[0].cloneNode(true) as HTMLInputElement;
}
+ /** Clears all existing children upon mount */
connectedCallback() {
for (let child of this.children) {
child.remove();
}
- this.addInstance();
+ this.#addInstance();
}
- createInstance(): HTMLInputElement {
- let new_el = this.el.cloneNode(true) as HTMLInputElement
+ /**
+ Instanciates a new instance of the input element.
+
+ An event listener for 'input' will be added, which will handle the
+ addition and removing of other elements.
+
+ All event listeners attachet on the input-list component will also be
+ added.
+ */
+ #createInstance(): HTMLInputElement {
+ let new_el = this.#el.cloneNode(true) as HTMLInputElement
let that = this;
new_el.addEventListener('input', function() {
/* TODO .value is empty both if it's actually empty, but also
@@ -49,7 +85,7 @@ class InputList extends HTMLElement {
}
} else {
if (!this.nextElementSibling) {
- that.addInstance();
+ that.#addInstance();
// window.setTimeout(() => this.focus())
this.focus();
}
@@ -63,8 +99,9 @@ class InputList extends HTMLElement {
return new_el;
}
- addInstance() {
- let new_el = this.createInstance();
+ /** Add a new instance of the input element to the container */
+ #addInstance() {
+ let new_el = this.#createInstance();
this.appendChild(new_el);
}
@@ -72,7 +109,7 @@ class InputList extends HTMLElement {
* The value from each element, except the last which should always be empty.
* Has an unspecified type, since children:s value field might give non-strings.
*/
- get value(): any[] {
+ get value(): unknown[] {
let value_list = []
for (let child of this.children) {
value_list.push((child as any).value);
@@ -83,6 +120,12 @@ class InputList extends HTMLElement {
return value_list
}
+ /**
+ Overwrite the current value with a new one.
+
+ Each entry in the array will be mapped unto one instance of the template
+ input element. A final empty element will also be added.
+ */
set value(new_value: any[]) {
let all_equal = true;
@@ -111,17 +154,22 @@ class InputList extends HTMLElement {
/* clear dictionary */
values.set(value, false);
} else {
- let new_el = this.createInstance();
+ let new_el = this.#createInstance();
new_el.value = value;
output_list.push(new_el);
}
}
/* final, trailing, element */
- output_list.push(this.createInstance());
+ output_list.push(this.#createInstance());
this.replaceChildren(...output_list);
}
+ /**
+ Add an event listener to each of the inputs.
+
+ This basically works as the "regular" version.
+ */
addEventListener(type: string, proc: ((e: Event) => void)) {
// if (type != 'input') throw "Only input supported";
diff --git a/static/ts/components/popup-element.ts b/static/ts/components/popup-element.ts
index cc011ce3..a1e81f0e 100644
--- a/static/ts/components/popup-element.ts
+++ b/static/ts/components/popup-element.ts
@@ -90,6 +90,7 @@ class PopupElement extends ComponentVEvent {
this.replaceChildren(body);
}
+ /** ['visible'] */
static get observedAttributes() {
return ['visible'];
}
@@ -99,7 +100,7 @@ class PopupElement extends ComponentVEvent {
case 'visible':
if (newValue !== null)
/* Only run resize code when showing the popup */
- this.onVisibilityChange()
+ this.#onVisibilityChange()
break;
}
}
@@ -114,6 +115,7 @@ class PopupElement extends ComponentVEvent {
return this.hasAttribute('visible');
}
+ /** Set the visibility status of the component. */
set visible(isVisible: boolean) {
if (isVisible) {
this.setAttribute('visible', 'visible');
@@ -122,7 +124,7 @@ class PopupElement extends ComponentVEvent {
}
}
- private onVisibilityChange() {
+ #onVisibilityChange() {
console.log('here');
/* TODO better way to find root */
diff --git a/static/ts/components/slider.ts b/static/ts/components/slider.ts
index 48abc91b..8be66a73 100644
--- a/static/ts/components/slider.ts
+++ b/static/ts/components/slider.ts
@@ -1,24 +1,58 @@
-export { SliderInput }
+/**
+ <slider-input />
+
+ A Web Component implementing a slider with a corresponding number input.
+
+ TODO rename this file
+
+ ### Parameters
+
+ All of these are optional, see {@linkcode dflt} for defaults.
+
+ #### min
+ Minimum allowed value.
+
+ #### max
+ Maximum allowed value.
+
+ #### step
+ How large each step of the slider/number box should be.
+
+ @module
+*/
+
+export { SliderInput, Attribute, dflt }
import { makeElement } from '../lib'
+/** Defalut values for all attributes, if not given */
const dflt = {
min: 0,
max: 100,
step: 1,
}
+/** Valid attributes for SliderInput */
type Attribute = 'min' | 'max' | 'step'
+/**
+ Component displaying an input slider, together with a corresponding numerical
+ input
+*/
class SliderInput extends HTMLElement {
/* value a string since javascript kind of expects that */
- #value = "0";
- min = 0;
- max = 100;
- step = 1;
-
+ #value = "" + dflt.min
+ /** Minimum allowed value */
+ min = dflt.min
+ /** Maximum allowed value */
+ max = dflt.max
+ /** How large each step should be */
+ step = dflt.step
+
+ /** The HTML slider component */
readonly slider: HTMLInputElement;
+ /** The HTML number input component */
readonly textIn: HTMLInputElement;
constructor(min?: number, max?: number, step?: number, value?: number) {
@@ -48,8 +82,8 @@ class SliderInput extends HTMLElement {
value: this.value,
}) as HTMLInputElement
- this.slider.addEventListener('input', e => this.propagate(e));
- this.textIn.addEventListener('input', e => this.propagate(e));
+ this.slider.addEventListener('input', e => this.#propagate(e));
+ this.textIn.addEventListener('input', e => this.#propagate(e));
/* MUST be after sub components are bound */
this.value = "" + (value || this.getAttribute('value') || defaultValue);
@@ -59,7 +93,7 @@ class SliderInput extends HTMLElement {
this.replaceChildren(this.slider, this.textIn);
}
-
+ /** ['min', 'max', 'step'] */
static get observedAttributes(): Attribute[] {
return ['min', 'max', 'step']
}
@@ -75,19 +109,35 @@ class SliderInput extends HTMLElement {
this[name] = parseFloat(to || "" + dflt[name])
}
- propagate(e: Event) {
+ /**
+ Helper for updating the value attribute
+
+ Event listeners are bound on both the input elements, which both simply
+ call this. This procedure then updates the classes value field.
+
+ TODO `oninput`?
+ */
+ #propagate(e: Event) {
this.value = (e.target as HTMLInputElement).value;
if (e instanceof InputEvent && this.oninput) {
this.oninput(e);
}
}
+ /**
+ Set a new numerical value.
+
+ A number not possible due to the current `min`, `max`, and `step`
+ properties can be set and will work, the slider will however not
+ properly show it, but rather the closest value it can display.
+ */
set value(value: string) {
this.slider.value = value;
this.textIn.value = value;
this.#value = value;
}
+ /** Get the current numerical value */
get value(): string {
return this.#value;
}
diff --git a/static/ts/components/tab-group-element.ts b/static/ts/components/tab-group-element.ts
index ce532cec..bcd45b40 100644
--- a/static/ts/components/tab-group-element.ts
+++ b/static/ts/components/tab-group-element.ts
@@ -58,9 +58,12 @@ export { TabGroupElement }
*/
class TabGroupElement extends ComponentVEvent {
+ /** The container holding all the tabLabels */
readonly menu: HTMLElement;
+ /** Contents of each tab */
tabs: HTMLElement[] = [];
+ /** Label element of each tab */
tabLabels: HTMLElement[] = [];
constructor(uid?: string) {
@@ -166,7 +169,7 @@ class TabGroupElement extends ComponentVEvent {
this.tabLabels.push(tabLabel);
this.menu.appendChild(tabLabel);
- tabLabel.addEventListener('click', () => this.tabClickedCallback(tabLabel));
+ tabLabel.addEventListener('click', () => this.#tabClickedCallback(tabLabel));
this.style.setProperty('--tabcount', '' + this.tabs.length);
}
@@ -199,7 +202,7 @@ class TabGroupElement extends ComponentVEvent {
}
/* TODO replace querySelectors here with our already saved references */
- tabClickedCallback(tab: Element) {
+ #tabClickedCallback(tab: Element) {
/* hide all tab panels */
for (let tabcontent of this.querySelectorAll('[role="tabpanel"]')) {
@@ -218,7 +221,7 @@ class TabGroupElement extends ComponentVEvent {
}
- /* returns our rrule tab if we have one */
+ /** Return our rrule tab if we have one */
has_rrule_tab(): Element | false {
for (let child of this.children) {
if (child.firstChild! instanceof EditRRule) {
diff --git a/static/ts/components/vevent-block.ts b/static/ts/components/vevent-block.ts
index 374cf103..90460740 100644
--- a/static/ts/components/vevent-block.ts
+++ b/static/ts/components/vevent-block.ts
@@ -16,10 +16,16 @@ import { VEvent } from '../vevent'
import { parseDate, to_local } from '../lib'
-/* <vevent-block />
+/**
+ A graphical block in the inline view.
- A grahpical block in the week view.
-*/
+ The back-end links what should become these to elements in the sidebar
+ containing extra info, jumping between them using fragment links.
+ That functionality is removed when we replace the non-js fallback children of
+ these elements, but we instead link it to a
+ {@linkcode components/popup-element.PopupElement}
+ containing the detailed information, along with editing controls and more.
+ */
class ComponentBlock extends ComponentVEvent {
constructor(uid?: string) {
super(uid);
diff --git a/static/ts/components/vevent.ts b/static/ts/components/vevent.ts
index 1d400e1e..50ff4a30 100644
--- a/static/ts/components/vevent.ts
+++ b/static/ts/components/vevent.ts
@@ -16,9 +16,23 @@ export { ComponentVEvent }
import { vcal_objects } from '../globals'
import { VEvent } from '../vevent'
+/**
+ Base class for all Web Components closely linked with VEvents.
+
+ TODO document how templates work.
+
+ TODO document lifecycle, and how objects are fetched from the "global" store.
+ */
abstract class ComponentVEvent extends HTMLElement {
+ /**
+ The template for this event.
+
+ TODO document how this is populate
+ */
template?: HTMLTemplateElement
+
+ /** The UID of the VEvent we are tracking */
uid: string
/**
@@ -72,6 +86,11 @@ abstract class ComponentVEvent extends HTMLElement {
should take care of that some other way */
}
+ /**
+ Called when the component is mounted.
+
+ Redraws the target if the wanted object is available at that time.
+ */
connectedCallback() {
let uid = this.dataset.uid
if (uid) {