Skip to content

Commit

Permalink
fix(ts): Improve types for strict mode.
Browse files Browse the repository at this point in the history
  • Loading branch information
jayfreestone committed Jun 11, 2019
1 parent 6d5255d commit a1098a1
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 78 deletions.
159 changes: 81 additions & 78 deletions src/priorityPlus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ItemsChangedEvent,
} from './events/createEvent';
import createEventHandler from './events/eventHandler';
import DeepPartial from './types/DeepPartial';
import createMirror from './utils/createMirror';
import processTemplate from './utils/processTemplate';
import validateAndThrow from './validation';
Expand All @@ -28,17 +29,43 @@ enum StateModifiers {
PrimaryHidden = 'is-hiding-primary',
}

interface ElementRefs {
[El.Container]: HTMLElement;
clone: {
[El.Main]: HTMLElement;
[El.NavItems]: HTMLElement[];
[El.ToggleBtn]: HTMLElement;
};
primary: {
[El.Main]: HTMLElement;
[El.PrimaryNav]: HTMLElement;
[El.NavItems]: HTMLElement[];
[El.OverflowNav]: HTMLElement;
[El.ToggleBtn]: HTMLElement;
};
}

interface Instance {
eventListeners: Map<((eventDetail: object) => void), {
eventType: Events;
wrappedCallback: (eventDetail: object) => void;
}>;
itemMap: WeakMap<HTMLElement|Element, NavType>;
observer: IntersectionObserver;
}

interface Options {
classNames?: {
[El.Container]: string[],
[El.Main]: string[],
[El.PrimaryNavWrapper]: string[],
[El.PrimaryNav]: string[],
[El.OverflowNav]: string[],
[El.ToggleBtn]: string[],
classNames: {
[El.Container]: string[];
[El.Main]: string[];
[El.PrimaryNavWrapper]: string[];
[El.PrimaryNav]: string[];
[El.OverflowNav]: string[];
[El.ToggleBtn]: string[];
[El.NavItems]: string[];
};
defaultOverflowVisible?: boolean;
innerToggleTemplate?: string|((args: object) => string);
defaultOverflowVisible: boolean;
innerToggleTemplate: string|((args: object) => string);
}

const defaultOptions: Options = {
Expand All @@ -49,74 +76,47 @@ const defaultOptions: Options = {
[El.PrimaryNav]: ['p-plus__primary'],
[El.OverflowNav]: ['p-plus__overflow'],
[El.ToggleBtn]: ['p-plus__toggle-btn'],
[El.NavItems]: ['p-plus__primary-nav-item'],
},
defaultOverflowVisible: false,
innerToggleTemplate: 'More',
};

function priorityPlus(targetElem: HTMLElement, userOptions: Options = {}) {
function priorityPlus(targetElem: HTMLElement, userOptions: DeepPartial<Options> = {}) {
/**
* @todo: We shouldn't have to cast this as Options, however DeepPartial creates
* breaks the type of innerToggleTemplate (?).
*/
const options = {
...defaultOptions,
...userOptions,
classNames: { ...defaultOptions.classNames, ...userOptions.classNames },
} as Options;

const { classNames } = options;

/**
* The instance's event emitter.
*/
const eventHandler = createEventHandler();

/**
* A map of navigation list items to their current designation (either the
* primary nav or the overflow nav), based on if they 'fit'.
* 'Instance' state variables and misc references.
* Force a cast as we know we will initialise these.
*/
const inst: {
eventListeners: Map<((eventDetail: object) => void), {
eventType: Events,
wrappedCallback: (eventDetail: object) => void,
}>,
itemMap: WeakMap<HTMLElement|Element, NavType>,
observer: IntersectionObserver,
} = {
const inst: Instance = {
eventListeners: new Map(),
itemMap: new WeakMap(),
observer: undefined,
};

const options: Options = {
...defaultOptions,
...userOptions,
classNames: { ...defaultOptions.classNames, ...userOptions.classNames },
};

const { classNames } = options;
} as Instance;

/**
* References to DOM elements so we can easily retrieve them.
* Force a cast as we know we will initialise these.
*/
const el: {
[El.Container]: HTMLElement,
clone: {
[El.Main]: HTMLElement,
[El.NavItems]: HTMLElement[],
[El.ToggleBtn]: HTMLElement,
},
primary: {
[El.Main]: HTMLElement,
[El.PrimaryNav]: HTMLElement,
[El.NavItems]: HTMLElement[],
[El.OverflowNav]: HTMLElement,
[El.ToggleBtn]: HTMLElement,
},
} = {
[El.Container]: undefined,
clone: {
[El.Main]: undefined,
[El.NavItems]: undefined,
[El.ToggleBtn]: undefined,
},
primary: {
[El.Main]: undefined,
[El.PrimaryNav]: undefined,
[El.NavItems]: undefined,
[El.OverflowNav]: undefined,
[El.ToggleBtn]: undefined,
},
};
const el: ElementRefs = {
clone: {},
primary: {},
} as ElementRefs;

/**
* Gets an element's 'mirror' Map for the clone/primary navigation - e.g.
Expand Down Expand Up @@ -150,7 +150,7 @@ function priorityPlus(targetElem: HTMLElement, userOptions: Options = {}) {
${dv(El.PrimaryNav)}
class="${cn(El.PrimaryNav)}"
>
${Array.from(targetElem.children).map((elem: HTMLElement) => (
${Array.from(targetElem.children).map((elem: Element) => (
`<li ${dv(El.NavItems)}>${elem.innerHTML}</li>`
)).join('')}
</${targetElem.tagName}>
Expand Down Expand Up @@ -184,15 +184,15 @@ function priorityPlus(targetElem: HTMLElement, userOptions: Options = {}) {
const original = document.createRange().createContextualFragment(markup);
const cloned = original.cloneNode(true) as Element;

el.primary[El.Main] = original.querySelector(`[${dv(El.Main)}]`);
el.primary[El.PrimaryNav] = original.querySelector(`[${dv(El.PrimaryNav)}]`);
el.primary[El.NavItems] = Array.from(original.querySelectorAll(`[${dv(El.NavItems)}]`));
el.primary[El.OverflowNav] = original.querySelector(`[${dv(El.OverflowNav)}]`);
el.primary[El.ToggleBtn] = original.querySelector(`[${dv(El.ToggleBtn)}]`);
el.primary[El.Main] = original.querySelector(`[${dv(El.Main)}]`) as HTMLElement;
el.primary[El.PrimaryNav] = original.querySelector(`[${dv(El.PrimaryNav)}]`) as HTMLElement;
el.primary[El.NavItems] = Array.from(original.querySelectorAll(`[${dv(El.NavItems)}]`)) as HTMLElement[];
el.primary[El.OverflowNav] = original.querySelector(`[${dv(El.OverflowNav)}]`) as HTMLElement;
el.primary[El.ToggleBtn] = original.querySelector(`[${dv(El.ToggleBtn)}]`) as HTMLElement;

el.clone[El.Main] = cloned.querySelector(`[${dv(El.Main)}]`);
el.clone[El.NavItems] = Array.from(cloned.querySelectorAll(`[${dv(El.NavItems)}]`));
el.clone[El.ToggleBtn] = cloned.querySelector(`[${dv(El.ToggleBtn)}]`);
el.clone[El.Main] = cloned.querySelector(`[${dv(El.Main)}]`) as HTMLElement;
el.clone[El.NavItems] = Array.from(cloned.querySelectorAll(`[${dv(El.NavItems)}]`)) as HTMLElement[];
el.clone[El.ToggleBtn] = cloned.querySelector(`[${dv(El.ToggleBtn)}]`) as HTMLElement;

el.clone[El.Main].setAttribute('aria-hidden', 'true');
el.clone[El.Main].setAttribute('data-clone', 'true');
Expand All @@ -207,7 +207,8 @@ function priorityPlus(targetElem: HTMLElement, userOptions: Options = {}) {
// observer will run on-load anyway.
el.clone[El.NavItems].forEach(item => itemMap.set(item, El.PrimaryNav));

targetElem.parentNode.replaceChild(container, targetElem);
const parent = targetElem.parentNode as HTMLElement;
parent.replaceChild(container, targetElem);
}

/**
Expand Down Expand Up @@ -244,12 +245,12 @@ function priorityPlus(targetElem: HTMLElement, userOptions: Options = {}) {
el.clone[El.NavItems]
.filter(item => itemMap.get(item) === navType)
.forEach(item => {
newNav.appendChild(
getElemMirror(
el.clone[El.NavItems],
el.primary[El.NavItems],
).get(item),
);
const elem = getElemMirror(
el.clone[El.NavItems],
el.primary[El.NavItems],
).get(item) as HTMLElement;

newNav.appendChild(elem);
});

return newNav as HTMLElement;
Expand All @@ -260,9 +261,10 @@ function priorityPlus(targetElem: HTMLElement, userOptions: Options = {}) {
*/
function updateNav(navType: NavType) {
const newNav = generateNav(navType);
const parent = el.primary[navType].parentNode as HTMLElement;

// Replace the existing nav element in the DOM
el.primary[navType].parentNode.replaceChild(
parent.replaceChild(
newNav,
el.primary[navType],
);
Expand Down Expand Up @@ -389,7 +391,7 @@ function priorityPlus(targetElem: HTMLElement, userOptions: Options = {}) {
* Remove listeners and attempt to reset the DOM.
*/
function destroy() {
inst.observer.disconnect();
if (inst.observer) inst.observer.disconnect();

el.primary[El.ToggleBtn].removeEventListener('click', onToggleClick);

Expand All @@ -400,7 +402,8 @@ function priorityPlus(targetElem: HTMLElement, userOptions: Options = {}) {
});

// Attempt to reset the DOM back to how it was
el[El.Container].parentNode.replaceChild(targetElem, el[El.Container]);
const parent = el[El.Container].parentNode as HTMLElement;
parent.replaceChild(targetElem, el[El.Container]);
}

(function init() {
Expand Down
13 changes: 13 additions & 0 deletions src/types/DeepPartial.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Taken from ts-essentials
* @link https://github.com/krzkaczor/ts-essentials/blob/master/lib/types.ts#L9
*/
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends Array<infer U>
? Array<DeepPartial<U>>
: T[P] extends ReadonlyArray<infer X>
? ReadonlyArray<DeepPartial<X>>
: DeepPartial<T[P]>
};

export default DeepPartial;

0 comments on commit a1098a1

Please sign in to comment.